edu.cornell.kfs.fp.batch.service.impl.AdvanceDepositServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for edu.cornell.kfs.fp.batch.service.impl.AdvanceDepositServiceImpl.java

Source

package edu.cornell.kfs.fp.batch.service.impl;

import edu.cornell.kfs.fp.CuFPConstants;
import edu.cornell.kfs.fp.CuFPParameterConstants;
import edu.cornell.kfs.fp.batch.GenerateAdvanceDepositDocumentsStep;
import edu.cornell.kfs.fp.batch.LoadAchIncomeFileStep;
import edu.cornell.kfs.fp.batch.service.AdvanceDepositService;
import edu.cornell.kfs.fp.businessobject.AchIncomeFile;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileGroup;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransaction;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransactionDateTime;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransactionNote;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransactionOpenItemReference;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransactionPayerOrPayeeName;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransactionReference;
import edu.cornell.kfs.fp.businessobject.AchIncomeFileTransactionSet;
import edu.cornell.kfs.fp.businessobject.AchIncomeNote;
import edu.cornell.kfs.fp.businessobject.AchIncomeTransaction;
import edu.cornell.kfs.fp.businessobject.IncomingWireAchMapping;
import edu.cornell.kfs.sys.CUKFSConstants;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.coa.businessobject.ObjectType;
import org.kuali.kfs.fp.businessobject.AdvanceDepositDetail;
import org.kuali.kfs.fp.document.AdvanceDepositDocument;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
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.PhysicalFlatFileInformation;
import org.kuali.kfs.sys.batch.service.BatchInputFileService;
import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.mail.BodyMailMessage;
import org.kuali.kfs.sys.service.EmailService;
import org.kuali.kfs.sys.util.GlobalVariablesUtils;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.web.format.FormatException;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kew.api.document.Document;
import org.kuali.rice.kew.api.document.DocumentStatus;
import org.kuali.rice.kew.api.document.WorkflowDocumentService;
import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
import org.kuali.rice.kew.api.document.search.DocumentSearchResult;
import org.kuali.rice.kew.api.document.search.DocumentSearchResults;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.kfs.krad.bo.Attachment;
import org.kuali.kfs.krad.bo.Note;
import org.kuali.kfs.krad.exception.InvalidAddressException;
import org.kuali.kfs.krad.exception.ValidationException;
import org.kuali.kfs.krad.service.AttachmentService;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.service.NoteService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;

import javax.mail.MessagingException;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.RemoteException;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Portions Modified 04/2016 and Copyright Cornell University
 * <p/>
 * 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.
 * <p/>
 * 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.
 * <p/>
 * 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/>.
 */

/**
 Copyright Indiana University
 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/>.
 */
public class AdvanceDepositServiceImpl implements AdvanceDepositService {
    private static final Logger LOG = LogManager.getLogger(AdvanceDepositServiceImpl.class);
    private static final String ACH_TRANSACTION_SEQUENCE_NUMBER = "sequenceNumber";
    private static final int MAX_NOTE_SIZE = 800;
    private static final int MIN_NOTE_SIZE = 80;

    protected AttachmentService attachmentService;
    protected BankService bankService;
    protected BatchInputFileService batchInputFileService;
    protected BatchInputFileType batchInputFileType;
    protected BusinessObjectService businessObjectService;
    protected DateTimeService dateTimeService;
    protected DocumentService documentService;
    protected EmailService emailService;
    protected NoteService noteService;
    protected ParameterService parameterService;
    protected PersonService personService;

    protected String attachmentsDirectory;
    private int achTotalPostedTransactions;
    private int achTotalSkippedTransactions;
    private int wiredTotalPostedTransactions;
    private int wiredTotalSkippedTransactions;
    private KualiDecimal achTotalPostedTransactionAmount;
    private KualiDecimal achTotalSkippedTransactionAmount;
    private KualiDecimal wiredTotalPostedTransactionAmount;
    private KualiDecimal wiredTotalSkippedTransactionAmount;

    /**
     * @see AdvanceDepositService#createDocuments()
     */
    public void createDocuments() {
        List<AchIncomeTransaction> transactions = (List<AchIncomeTransaction>) businessObjectService
                .findMatchingOrderBy(AchIncomeTransaction.class, new HashMap<String, Object>(),
                        ACH_TRANSACTION_SEQUENCE_NUMBER, true);

        for (AchIncomeTransaction transaction : transactions) {
            AdvanceDepositDocument document = createAdvanceDepositDocument(transaction);
            saveDocument(document);
            createNotes(transaction, document);
        }
    }

    private void saveDocument(AdvanceDepositDocument document) {
        try {
            documentService.saveDocument(document);
            if (LOG.isInfoEnabled()) {
                LOG.info("Saved Advance Deposit document: " + document.getDocumentNumber());
            }
        } catch (Exception e) {
            LOG.error("Error persisting document # " + document.getDocumentHeader().getDocumentNumber() + " "
                    + e.getMessage(), e);
            throw new RuntimeException("Error persisting document # "
                    + document.getDocumentHeader().getDocumentNumber() + " " + e.getMessage(), e);
        }
    }

    /**
     * @see AdvanceDepositService#routeAdvanceDepositDocuments()
     */
    public boolean routeAdvanceDepositDocuments() {
        List<String> documentIdList;

        try {
            documentIdList = retrieveAdvanceDepositDocumentsToRoute(KewApiConstants.ROUTE_HEADER_SAVED_CD);
        } catch (WorkflowException | RemoteException e) {
            LOG.error("Error retrieving advance deposit documents for routing: " + e.getMessage(), e);
            throw new RuntimeException(e.getMessage(), e);
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("Advance deposit to Route: " + documentIdList);
        }

        for (String documentId : documentIdList) {
            try {
                AdvanceDepositDocument advanceDocument = (AdvanceDepositDocument) documentService
                        .getByDocumentHeaderId(documentId);
                if (LOG.isInfoEnabled()) {
                    LOG.info("Routing advance deposit document # " + documentId + ".");
                }
                documentService.routeDocument(advanceDocument, "document routed by achIncome batch job", null);
            } catch (Exception e) {
                LOG.error("Error routing document # " + documentId + " due to exception: " + e.getMessage());
                logException(e);
            }
        }

        return true;
    }

    private void logException(Exception e) {
        if (e instanceof ValidationException) {
            List<String> errors = GlobalVariablesUtils.extractGlobalVariableErrors();
            if (ObjectUtils.isNotNull(errors) && !errors.isEmpty()) {
                for (String errorMessage : errors) {
                    LOG.error(errorMessage);
                }
            }
        } else {
            LOG.error(e.getStackTrace());
        }
    }

    /**
      * Returns a list of all initiated but not yet routed advance deposit documents, using the WorkflowDocumentService.
      *
      * @return a list of advance deposit documents to route
      */
    protected List<String> retrieveAdvanceDepositDocumentsToRoute(String statusCode)
            throws WorkflowException, RemoteException {
        List<String> documentIds = new ArrayList<String>();

        List<DocumentStatus> routeStatuses = new ArrayList<DocumentStatus>();
        routeStatuses.add(DocumentStatus.fromCode(statusCode));

        Person systemUser = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER);
        String principalName = systemUser.getPrincipalName();

        DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create();
        criteria.setDocumentTypeName(KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT);
        criteria.setDocumentStatuses(routeStatuses);
        criteria.setInitiatorPrincipalName(principalName);

        DocumentSearchResults results = getWorkflowDocumentService().documentSearch(systemUser.getPrincipalId(),
                criteria.build());

        for (DocumentSearchResult resultRow : results.getSearchResults()) {
            Document document = resultRow.getDocument();
            if (ObjectUtils.isNotNull(document)) {
                documentIds.add(document.getDocumentId());
            }
        }

        return documentIds;
    }

    /**
     * Creates a AdvanceDepositDocument from the List of transactions given.
     *
     * @param transaction List of CashReceiptDocument objects to be used for creating the document.
     * @return A AdvanceDepositDocument populated with the transactions provided.
     */
    protected AdvanceDepositDocument createAdvanceDepositDocument(AchIncomeTransaction transaction) {
        AdvanceDepositDocument advanceDepositDocument;

        try {
            String detailReferenceNumber = parameterService.getParameterValueAsString(
                    GenerateAdvanceDepositDocumentsStep.class,
                    CuFPParameterConstants.AdvanceDepositDocument.DETAIL_REFERENCE_NUMBER);
            String detailDescription = parameterService.getParameterValueAsString(
                    GenerateAdvanceDepositDocumentsStep.class,
                    CuFPParameterConstants.AdvanceDepositDocument.DETAIL_DESCRIPTION);
            String bankCode = parameterService.getParameterValueAsString(GenerateAdvanceDepositDocumentsStep.class,
                    CuFPParameterConstants.AdvanceDepositDocument.BANK_CODE);
            String documentDescription = parameterService.getParameterValueAsString(
                    GenerateAdvanceDepositDocumentsStep.class,
                    CuFPParameterConstants.AdvanceDepositDocument.DOCUMENT_DESCRIPTION);

            advanceDepositDocument = (AdvanceDepositDocument) documentService
                    .getNewDocument(KFSConstants.FinancialDocumentTypeCodes.ADVANCE_DEPOSIT);
            advanceDepositDocument.getDocumentHeader().setDocumentDescription(documentDescription);
            advanceDepositDocument.setCampusLocationCode(CuFPConstants.ADVANCE_DEPOSIT_DEFAULT_CAMPUS_CODE);
            advanceDepositDocument.setDepositDate(dateTimeService.convertToSqlDate(transaction.getBankTimestamp()));

            createSourceAccountingLine(transaction, advanceDepositDocument);

            AdvanceDepositDetail advanceDepositDetail = new AdvanceDepositDetail();
            advanceDepositDetail.setDocumentNumber(advanceDepositDocument.getDocumentNumber());
            advanceDepositDetail.setFinancialDocumentAdvanceDepositDate(
                    dateTimeService.convertToSqlDate(transaction.getLoadTimestamp()));
            advanceDepositDetail.setFinancialDocumentAdvanceDepositAmount(transaction.getTransactionAmount());
            advanceDepositDetail.setFinancialDocumentAdvanceDepositReferenceNumber(detailReferenceNumber);
            advanceDepositDetail.setFinancialDocumentAdvanceDepositDescription(detailDescription);
            advanceDepositDetail.setFinancialDocumentBankCode(bankCode);
            Bank bank = bankService.getByPrimaryId(bankCode);
            advanceDepositDetail.setBank(bank);

            advanceDepositDocument.addAdvanceDeposit(advanceDepositDetail);
        } catch (WorkflowException | ParseException e) {
            LOG.error("Error creating advance deposit documents: " + e.getMessage(), e);
            throw new RuntimeException("Error creating advance deposit documents: " + e.getMessage(), e);
        }

        return advanceDepositDocument;
    }

    private void createNotes(AchIncomeTransaction transaction, AdvanceDepositDocument document) {
        String fileName = CuFPConstants.ADVANCE_DEPOSIT_NOTE_FILE_PREFIX + document.getDocumentNumber() + "_"
                + new SimpleDateFormat("yyyyMMdd_HHmmss").format(dateTimeService.getCurrentDate()) + ".txt";
        StringBuilder notes = new StringBuilder();

        for (AchIncomeNote achIncomeNote : transaction.getNotes()) {
            notes.append(achIncomeNote.getNoteText());
            notes.append("\n");
        }

        byte[] notesAttachmentBytes = notes.toString().getBytes();
        String attachmentType = null;

        try {
            Attachment attachment = attachmentService.createAttachment(document.getDocumentHeader(), fileName,
                    "text", notesAttachmentBytes.length, new ByteArrayInputStream(notesAttachmentBytes),
                    attachmentType);
            Note note = documentService.createNoteFromDocument(document,
                    "Attachment with transaction notes created by ach/incoming wire batch job.");
            note.setAttachment(attachment);
            attachment.setNote(note);
            noteService.save(note);
        } catch (IOException e) {
            LOG.error("Error while adding notes to advance deposit documents: " + e.getMessage(), e);
            throw new RuntimeException("Error while adding notes to the document " + document.getDocumentNumber());
        }
    }

    protected void createSourceAccountingLine(AchIncomeTransaction transaction,
            AdvanceDepositDocument advanceDepositDocument) {
        String chart = parameterService.getParameterValueAsString(GenerateAdvanceDepositDocumentsStep.class,
                CuFPParameterConstants.AdvanceDepositDocument.CHART);
        String objectCode = parameterService.getParameterValueAsString(GenerateAdvanceDepositDocumentsStep.class,
                CuFPParameterConstants.AdvanceDepositDocument.OBJECT_CODE);
        String account = parameterService.getParameterValueAsString(GenerateAdvanceDepositDocumentsStep.class,
                CuFPParameterConstants.AdvanceDepositDocument.ACCOUNT);

        IncomingWireAchMapping matchingIncomingWireAchMapping = null;
        Collection<IncomingWireAchMapping> incomingWireAchMappings = businessObjectService
                .findAll(IncomingWireAchMapping.class);

        for (IncomingWireAchMapping mapping : incomingWireAchMappings) {
            List<AchIncomeNote> notes = transaction.getNotes();
            if (doNotesMatch(mapping, notes)) {
                matchingIncomingWireAchMapping = mapping;
                break;
            }
        }

        if (ObjectUtils.isNotNull(matchingIncomingWireAchMapping)) {
            chart = matchingIncomingWireAchMapping.getChartOfAccountsCode();
            objectCode = matchingIncomingWireAchMapping.getFinancialObjectCode();
            account = matchingIncomingWireAchMapping.getAccountNumber();
        }

        setupSourceAccountingLine(transaction, advanceDepositDocument, chart, objectCode, account);
    }

    protected boolean doNotesMatch(IncomingWireAchMapping mapping, List<AchIncomeNote> notes) {
        final String shortDescription = mapping.getShortDescription();
        for (AchIncomeNote note : notes) {
            if (note.getNoteText().indexOf(shortDescription) >= 0) {
                return true;
            }
        }
        return false;
    }

    protected void setupSourceAccountingLine(AchIncomeTransaction transaction,
            AdvanceDepositDocument advanceDepositDocument, String chart, String objectCode, String account) {
        SourceAccountingLine sourceAccountingLine = new SourceAccountingLine();
        sourceAccountingLine.setSequenceNumber(advanceDepositDocument.getNextSourceLineNumber());
        sourceAccountingLine.setChartOfAccountsCode(chart);
        sourceAccountingLine.setPostingYear(getSourceAccountingLinePostingYear(sourceAccountingLine));
        sourceAccountingLine.setFinancialObjectCode(objectCode);
        setSourceAccountingLineAccountNumber(account, sourceAccountingLine);
        sourceAccountingLine.setFinancialDocumentLineDescription(transaction.getPayerName());
        setSourceAccountingLineAmount(transaction, sourceAccountingLine, chart, objectCode);
        sourceAccountingLine.setOverrideCode(AccountingLineOverride.CODE.NONE);
        sourceAccountingLine.setPostingYear(advanceDepositDocument.getPostingYear());
        sourceAccountingLine.setDocumentNumber(advanceDepositDocument.getDocumentNumber());
        List<SourceAccountingLine> sourceLines = new ArrayList<>();
        sourceLines.add(sourceAccountingLine);
        advanceDepositDocument.setSourceAccountingLines(sourceLines);
    }

    protected void setSourceAccountingLineAmount(AchIncomeTransaction transaction,
            SourceAccountingLine sourceAccountingLine, String chart, String objectCode) {
        KualiDecimal amount = transaction.getTransactionAmount();
        String objectTypeCode = getObjectCodeType(chart, objectCode);

        if (CUKFSConstants.BasicAccountingCategory.ASSET.equalsIgnoreCase(objectTypeCode)
                || CUKFSConstants.BasicAccountingCategory.EXPENSE.equalsIgnoreCase(objectTypeCode)) {
            amount = amount.negated();
        }
        sourceAccountingLine.setAmount(amount);

    }

    protected String getObjectCodeType(String chart, String objectCode) {
        Map<String, String> keys = new HashMap<String, String>();
        keys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chart);
        keys.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, objectCode);

        ObjectCode objectCodeInfo = businessObjectService.findByPrimaryKey(ObjectCode.class, keys);
        objectCodeInfo.refreshReferenceObject(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE);
        ObjectType objectType = objectCodeInfo.getFinancialObjectType();
        String objectTypeCode = objectType.getBasicAccountingCategoryCode();

        return objectTypeCode;
    }

    /**
     * This is split out so this class can be more easily tested.
     * @param account
     * @param sourceAccountingLine
     */
    protected void setSourceAccountingLineAccountNumber(String account, SourceAccountingLine sourceAccountingLine) {
        sourceAccountingLine.setAccountNumber(account);
    }

    /**
     * This is split out so this class can be more easily tested.
     *
     * @param sourceAccountingLine
     * @return
     */
    protected Integer getSourceAccountingLinePostingYear(SourceAccountingLine sourceAccountingLine) {
        return sourceAccountingLine.getPostingYear();
    }

    /**
     * @see AdvanceDepositService#loadFile()
     */
    public void loadFile() {
        List<PhysicalFlatFileInformation> flatFileInformationList = new ArrayList<>();
        PhysicalFlatFileInformation physicalflatFileInformation;
        List<String> fileNamesToLoad = getListOfFilesToProcess();
        if (LOG.isInfoEnabled()) {
            LOG.info("Found " + fileNamesToLoad.size() + " file(s) to process.");
        }

        for (String inputFileName : fileNamesToLoad) {
            try {
                if (LOG.isInfoEnabled()) {
                    LOG.info("Beginning processing of filename: " + inputFileName + ".");
                }
                physicalflatFileInformation = new PhysicalFlatFileInformation(inputFileName);
                flatFileInformationList.add(physicalflatFileInformation);

                if (loadFile(inputFileName, physicalflatFileInformation)) {
                    physicalflatFileInformation.addFileInfoMessage("File successfully completed processing.");
                } else {
                    physicalflatFileInformation.addFileErrorMessage("Unable to process file.");
                }
            } catch (RuntimeException e) {
                LOG.error("Caught exception trying to load: " + inputFileName, e);
                throw new RuntimeException("Caught exception trying to load: " + inputFileName, e);
            } finally {
                removeDoneFile(inputFileName);
            }
        }

        sendEmailSummary(flatFileInformationList);
    }

    public boolean loadFile(String fileName, PhysicalFlatFileInformation physicalFlatFileInformation) {
        boolean valid = true;
        byte[] fileByteContent = safelyLoadFileBytes(fileName);

        if (LOG.isInfoEnabled()) {
            LOG.info("Attempting to parse the file ");
        }
        Object parsedObject;

        try {
            parsedObject = batchInputFileService.parse(batchInputFileType, fileByteContent);
        } catch (org.kuali.kfs.sys.exception.ParseException e) {
            LOG.error("Error parsing batch file: " + e.getMessage());
            FlatFileInformation fileInformation = new FlatFileInformation();
            fileInformation.addFileInfoMessage("Unable to process file"
                    + StringUtils.substringAfterLast(fileName, "\\") + "." + e.getMessage());
            physicalFlatFileInformation.getFlatFileInfomationList().add(fileInformation);
            return false;
        }

        if (parsedObject != null) {
            valid = validate(parsedObject);
            copyAllMessage(parsedObject, physicalFlatFileInformation);
            if (valid) {
                loadAchIncomeTransactions(parsedObject);
            }
        }

        return valid;
    }

    public boolean validate(Object parsedFileContents) {
        boolean valid = true;
        List<AchIncomeFile> achIncomeFiles = (ArrayList<AchIncomeFile>) parsedFileContents;
        for (AchIncomeFile achIncomeFile : achIncomeFiles) {
            initializeTransactionCountsAndAmounts();
            String payerMessage = "";

            if (validateTrailerRecord(achIncomeFile)) {
                valid &= validateGroupCount(achIncomeFile);
                valid &= validateLogicFileControlNumbers(achIncomeFile);
            } else {
                valid = false;
            }

            for (AchIncomeFileGroup achIncomeFileGroup : achIncomeFile.getGroups()) {
                if (validateGroupTrailer(achIncomeFileGroup)) {
                    valid &= validateTransactionCount(achIncomeFileGroup);
                    valid &= validateGroupControlNumber(achIncomeFileGroup);
                } else {
                    valid = false;
                }

                validateFunctionalIdentifierCode(achIncomeFile, achIncomeFileGroup);

                for (AchIncomeFileTransactionSet achIncomeFileTransactionSet : achIncomeFileGroup
                        .getTransactionSets()) {
                    if (validateTransactionTrailer(achIncomeFileTransactionSet)) {
                        valid &= validateTransactionSetControlNumbers(achIncomeFileTransactionSet);
                    } else {
                        valid = false;
                    }

                    // loop through the AchTransaction to get the total transaction count and amount
                    // for ACH and wired posted and skipped transactions
                    setAchIncomeTransactionCountsAndAmounts(achIncomeFileTransactionSet.getTransactionGuts());
                    payerMessage += validatePayerName(achIncomeFileTransactionSet.getTransactionGuts());
                }
            }

            valid &= validateTimestamp(achIncomeFile);
            achIncomeFile.setEmailMessageText(getEmailMessageText(achIncomeFile, payerMessage));
        }

        if (LOG.isInfoEnabled()) {
            LOG.info("validate method executing");
        }

        return valid;
    }

    protected void initializeTransactionCountsAndAmounts() {
        achTotalPostedTransactions = 0;
        achTotalSkippedTransactions = 0;
        wiredTotalPostedTransactions = 0;
        wiredTotalSkippedTransactions = 0;
        achTotalPostedTransactionAmount = new KualiDecimal(0.00);
        achTotalSkippedTransactionAmount = new KualiDecimal(0.00);
        wiredTotalPostedTransactionAmount = new KualiDecimal(0.00);
        wiredTotalSkippedTransactionAmount = new KualiDecimal(0.00);
    }

    /**
     * verify there is an ISA and IEA record for each logical file
     *
     * @param achIncomeFile ach income file to validate
     * @return whether the trailer record is valid (i.e. it exists)
     */
    protected boolean validateTrailerRecord(AchIncomeFile achIncomeFile) {
        if (ObjectUtils.isNull(achIncomeFile.getTrailer())) {
            String message = "No logical file trailer found for file header :"
                    + achIncomeFile.getInterchangeControlNumber();
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    protected boolean validateGroupCount(AchIncomeFile achIncomeFile) {
        int totalGroups = achIncomeFile.getTrailer().getTotalGroups();
        if (totalGroups != achIncomeFile.getGroups().size()) {
            String message = "The group count on the file trailer," + totalGroups + ","
                    + "does not match the number of groups," + achIncomeFile.getGroups().size() + ",in the file:"
                    + achIncomeFile.getInterchangeControlNumber();
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    protected boolean validateLogicFileControlNumbers(AchIncomeFile achIncomeFile) {
        if (!achIncomeFile.getInterchangeControlNumber()
                .equals(achIncomeFile.getTrailer().getInterchangeControlNumber())) {
            String message = "Cannot match logical file header to file trailer for file: ISA Control Number ["
                    + achIncomeFile.getInterchangeControlNumber() + "] " + "IEA Control Number ["
                    + achIncomeFile.getTrailer().getInterchangeControlNumber() + "]";
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    /**
     * verify there is an GS and GE record for each logical file
     *
     * @param achIncomeFileGroup ach income file group to validate
     * @return true if valid, false if otherwise
     */
    protected boolean validateGroupTrailer(AchIncomeFileGroup achIncomeFileGroup) {
        if (ObjectUtils.isNull(achIncomeFileGroup.getGroupTrailer())) {
            String message = "No group trailer found for group: [" + achIncomeFileGroup.getGroupControlNumber()
                    + "]";
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    /**
     * verify the group trailer transactionSets count matches the numbers of transactions
     *
     * @param achIncomeFileGroup ach income file group to validate
     * @return true if valid, false if otherwise
     */
    protected boolean validateTransactionCount(AchIncomeFileGroup achIncomeFileGroup) {
        int totalTransactionSets = achIncomeFileGroup.getGroupTrailer().getTotalTransactionSets();
        if (totalTransactionSets != achIncomeFileGroup.getTransactionSets().size()) {
            String message = "The transaction count on the group trailer, " + totalTransactionSets
                    + ", does not match the number of transactions, "
                    + achIncomeFileGroup.getTransactionSets().size() + ", in the group: ["
                    + achIncomeFileGroup.getGroupControlNumber() + "]";
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    /**
     * verify that group control number on GS and GE match
     *
     * @param achIncomeFileGroup ach income file group to validate
     * @return true if valid, false if otherwise
     */
    protected boolean validateGroupControlNumber(AchIncomeFileGroup achIncomeFileGroup) {
        if (!achIncomeFileGroup.getGroupControlNumber()
                .equals(achIncomeFileGroup.getGroupTrailer().getGroupControlNumber())) {
            String message = "Cannot match group header to group trailer for group: GS Control Number ["
                    + achIncomeFileGroup.getGroupControlNumber() + "] " + "IEA Control Number ["
                    + achIncomeFileGroup.getGroupTrailer().getGroupControlNumber() + "]";
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    /**
     * verify RA is on the functional identifier code on the GS record
     *
     * @param achIncomeFile ach income file to validate
     * @param achIncomeFileGroup ach income file group to validate
     */
    protected void validateFunctionalIdentifierCode(AchIncomeFile achIncomeFile,
            AchIncomeFileGroup achIncomeFileGroup) {
        if (!StringUtils.equals(CuFPConstants.AchIncomeFileGroup.GROUP_FUNCTIONAL_IDENTIFIER_CD_RA,
                achIncomeFileGroup.getGroupFunctionIdentifierCode())) {
            String message = "The Functional Identifier Code is not "
                    + CuFPConstants.AchIncomeFileGroup.GROUP_FUNCTIONAL_IDENTIFIER_CD_RA + " for group: "
                    + achIncomeFileGroup.getGroupControlNumber() + "-"
                    + achIncomeFileGroup.getGroupFunctionIdentifierCode();
            achIncomeFile.getFlatFileTransactionInformation().addWarnMessage(message);
            GlobalVariables.getMessageMap().putWarning(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
        }
    }

    /**
     * verify there is an ST and SE record for each logical file
     *
     * @param achIncomeFileTransactionSet ach income file transaction set to validate
     * @return true if valid, false if otherwise
     */
    protected boolean validateTransactionTrailer(AchIncomeFileTransactionSet achIncomeFileTransactionSet) {
        if (ObjectUtils.isNull(achIncomeFileTransactionSet.getTransactionSetTrailer())) {
            String message = "No transaction trailer found for transaction: ["
                    + achIncomeFileTransactionSet.getTransactionSetControlNumber() + "]";
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    /**
     * verify that the transaction set control number on SE and ST match
     *
     * @param achIncomeFileTransactionSet ach income file transaction set to validate
     * @return true if valid, false if otherwise
     */
    protected boolean validateTransactionSetControlNumbers(
            AchIncomeFileTransactionSet achIncomeFileTransactionSet) {
        if (!achIncomeFileTransactionSet.getTransactionSetControlNumber()
                .equals(achIncomeFileTransactionSet.getTransactionSetTrailer().getTransactionSetControlNumber())) {
            String message = "Cannot match transaction header to transaction trailer for transaction set: ST Control Number: ["
                    + achIncomeFileTransactionSet.getTransactionSetControlNumber() + "] " + "SE Control Number: ["
                    + achIncomeFileTransactionSet.getTransactionSetTrailer().getTransactionSetControlNumber() + "]";
            LOG.error("Error while validating the Ach Income file" + message);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    message);
            return false;
        }

        return true;
    }

    private String validatePayerName(List<AchIncomeFileTransaction> achIncomeFileTransaction) {
        StringBuilder payerMessage = new StringBuilder();

        for (AchIncomeFileTransaction transaction : achIncomeFileTransaction) {
            if (StringUtils.equals(CuFPConstants.AchIncomeFileTransaction.PAYER_NOT_IDENTIFIED,
                    transaction.getPayerName())) {
                payerMessage.append("Payer Name was not found for transaction amount $"
                        + getFormattedAmount("##,##,##0.00", transaction.getTransactionAmount()));
                payerMessage.append(" [Date: ");
                payerMessage.append(transaction.getEffectiveDate());
                payerMessage.append("]");
                payerMessage.append("\n");
            }
        }

        return payerMessage.toString();
    }

    protected boolean validateTimestamp(AchIncomeFile achIncomeFile) {
        try {
            getFormattedTimestamp(achIncomeFile, "fileDate/Time");
        } catch (FormatException e) {
            LOG.error("Error while validating the ACH Income file: " + e.getMessage());
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_CUSTOM,
                    e.getMessage());
            return false;
        }

        return true;
    }

    public void sendEmailSummary(List<PhysicalFlatFileInformation> flatFileInformationList) {
        for (PhysicalFlatFileInformation physicalFlatFileInformation : flatFileInformationList) {
            List<FlatFileInformation> fileInformations = physicalFlatFileInformation.getFlatFileInfomationList();
            for (FlatFileInformation fileInformation : fileInformations) {
                sendEmail(fileInformation);
            }
        }
    }

    private void sendEmail(FlatFileInformation fileInformation) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("sendEmail() starting");
        }
        BodyMailMessage message = new BodyMailMessage();

        String returnAddress = parameterService.getParameterValueAsString(LoadAchIncomeFileStep.class,
                CuFPParameterConstants.AchIncome.ACH_INCOME_SUMMARY_FROM_EMAIL_ADDRESS);
        if (StringUtils.isEmpty(returnAddress)) {
            returnAddress = emailService.getDefaultFromAddress();
        }
        message.setFromAddress(returnAddress);
        String subject = parameterService.getParameterValueAsString(LoadAchIncomeFileStep.class,
                CuFPParameterConstants.AchIncome.ACH_INCOME_SUMMARY_EMAIL_SUBJECT);

        message.setSubject(subject);
        List<String> toAddressList = new ArrayList<>(
                parameterService.getParameterValuesAsString(LoadAchIncomeFileStep.class,
                        CuFPParameterConstants.AchIncome.ACH_INCOME_SUMMARY_TO_EMAIL_ADDRESSES));
        message.getToAddresses().addAll(toAddressList);
        String body = composeAchIncomeSummaryEmailBody(fileInformation);
        message.setMessage(body);

        emailService.sendMessage(message, false);
    }

    private String composeAchIncomeSummaryEmailBody(FlatFileInformation flatFileInformation) {
        StringBuilder body = new StringBuilder();

        for (String[] resultMessage : flatFileInformation.getMessages()) {
            body.append(resultMessage[1]);
            body.append("\n");
        }

        for (Object object : flatFileInformation.getFlatFileIdentifierToTransactionInfomationMap().values()) {
            for (String[] message : ((FlatFileTransactionInformation) object).getMessages()) {
                body.append(message[1]);
                body.append("\n");
            }
        }

        return body.toString();
    }

    protected List<String> getListOfFilesToProcess() {
        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.");
        }

        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) {
        InputStream fileContents;
        byte[] fileByteContent;
        try {
            fileContents = new FileInputStream(fileName);
        } catch (FileNotFoundException e1) {
            LOG.error("Batch file not found [" + fileName + "]. " + e1.getMessage());
            throw new RuntimeException("Batch File not found [" + fileName + "]. " + e1.getMessage());
        }
        try {
            fileByteContent = IOUtils.toByteArray(fileContents);
        } catch (IOException e1) {
            LOG.error("IO Exception loading: [" + fileName + "]. " + e1.getMessage());
            throw new RuntimeException("IO Exception loading: [" + fileName + "]. " + e1.getMessage());
        } finally {
            IOUtils.closeQuietly(fileContents);
        }

        return fileByteContent;
    }

    /**
     * Clears out associated .done files for the processed data files.
     * @param dataFileName
     */
    protected void removeDoneFile(String dataFileName) {
        File doneFile = new File(StringUtils.substringBeforeLast(dataFileName, ".") + ".done");
        if (doneFile.exists()) {
            doneFile.delete();
        }
    }

    private void loadAchIncomeTransactions(Object parsedObject) {
        List<AchIncomeTransaction> achIncomeTransactions = new ArrayList();
        List<AchIncomeFile> achIncomeFiles = (List<AchIncomeFile>) parsedObject;
        for (AchIncomeFile achIncomeFile : achIncomeFiles) {
            setAchTransactions(achIncomeFile, achIncomeTransactions);
        }

        saveTransaction(achIncomeTransactions);
    }

    private void saveTransaction(List<AchIncomeTransaction> achIncomeFileTransactions) {
        businessObjectService.save(achIncomeFileTransactions);
    }

    /**
     * Calls businessObjectService to remove all the ach Income transaction rows from the transaction load table.
     */
    public void cleanTransactionsTable() {
        businessObjectService.deleteMatching(AchIncomeTransaction.class, new HashMap<String, Object>());
    }

    protected void setAchTransactions(AchIncomeFile achIncomeFile, List achIncomeTransactions) {
        Timestamp bankTimestamp = getFormattedTimestamp(achIncomeFile, "fileDate/time");

        for (AchIncomeFileGroup achIncomeFileGroup : achIncomeFile.getGroups()) {
            for (AchIncomeFileTransactionSet achIncomeFileTransactionSet : achIncomeFileGroup
                    .getTransactionSets()) {
                for (AchIncomeFileTransaction achIncomeFileTransaction : achIncomeFileTransactionSet
                        .getTransactionGuts()) {
                    if (KFSConstants.GL_CREDIT_CODE.equals(achIncomeFileTransaction.getCreditDebitIndicator())) {
                        AchIncomeTransaction achIncomeTransaction = new AchIncomeTransaction();
                        achIncomeTransaction.setPaymentMethodCode(achIncomeFileTransaction.getPaymentMethodCode());
                        achIncomeTransaction.setEffectiveDate(achIncomeFileTransaction.getEffectiveDate());
                        achIncomeTransaction.setLoadTimestamp(dateTimeService.getCurrentTimestamp());
                        achIncomeTransaction.setBankTimestamp(bankTimestamp);
                        achIncomeTransaction.setTransactionAmount(achIncomeFileTransaction.getTransactionAmount());
                        achIncomeTransaction.setPayerName(truncatePayerNameIfNecessary(achIncomeFileTransaction));
                        achIncomeTransaction.setNotes(createNotes(achIncomeFileTransaction));
                        achIncomeTransactions.add(achIncomeTransaction);
                    }
                }
            }
        }
    }

    protected String truncatePayerNameIfNecessary(AchIncomeFileTransaction achIncomeFileTransaction) {
        String payerName = achIncomeFileTransaction.getPayerName();

        if (payerName.length() > CuFPConstants.AchIncomeFileTransactionPayerOrPayeeName.ACH_TRN_PAYER_NM_DB_SIZE) {
            payerName = payerName.substring(0,
                    CuFPConstants.AchIncomeFileTransactionPayerOrPayeeName.ACH_TRN_PAYER_NM_DB_SIZE);
        }

        return payerName;
    }

    private List<AchIncomeNote> createNotes(AchIncomeFileTransaction achIncomeFileTransaction) {
        StringBuilder notes = addPaymentInformationToNotes(achIncomeFileTransaction);
        List<String> achNotes = addRMRAndNTELinesToNotes(achIncomeFileTransaction, notes);

        List<AchIncomeNote> achIncomeNotes = new ArrayList<>();
        int noteLineNumber = 1;
        for (String noteText : achNotes) {
            AchIncomeNote achIncomeNote = new AchIncomeNote();
            achIncomeNote.setNoteLineNumber(noteLineNumber);
            achIncomeNote.setNoteText(noteText);
            achIncomeNotes.add(achIncomeNote);
            noteLineNumber++;
        }

        return achIncomeNotes;
    }

    private StringBuilder addPaymentInformationToNotes(AchIncomeFileTransaction achIncomeFileTransaction) {
        StringBuilder notes = new StringBuilder();

        if (CuFPConstants.AchIncomeFileTransaction.TRANS_PAYMENT_METHOD_ACH
                .equals(achIncomeFileTransaction.getPaymentMethodCode())) {
            notes.append("ACH PAYMENT INFORMATION: ");
        }

        if (CuFPConstants.AchIncomeFileTransaction.TRANS_PAYMENT_METHOD_FWT
                .equals(achIncomeFileTransaction.getPaymentMethodCode())) {
            notes.append("WIRE PAYMENT INFORMATION: ");
        }
        notes.append("\n");
        notes.append("CREDIT: ");
        notes.append("$");
        notes.append(getFormattedAmount("##,##,###.00", achIncomeFileTransaction.getTransactionAmount()));
        notes.append(" ");
        notes.append(achIncomeFileTransaction.getCreditDebitIndicator());
        notes.append("\n");
        notes.append("EFFECTIVE DATE: ");
        notes.append(achIncomeFileTransaction.getEffectiveDate());
        notes.append("\n");
        notes.append("COMPANY ID: ");
        notes.append(achIncomeFileTransaction.getCompanyId());
        notes.append("\n");

        if (achIncomeFileTransaction.getTrace() != null) {
            notes.append("TRACE NUMBER: ");
            notes.append(achIncomeFileTransaction.getTrace().getTraceNumber());
            notes.append("\n");
        }

        List<AchIncomeFileTransactionReference> fileTransactionReferences = achIncomeFileTransaction
                .getReferences();
        for (AchIncomeFileTransactionReference fileTransactionReference : fileTransactionReferences) {
            if (CuFPConstants.AchIncomeFileTransactionReference.REF_REFERENCE_TYPE_TN
                    .equals(fileTransactionReference.getType())) {
                notes.append("TRANSACTION NUMBER: ");
                notes.append(fileTransactionReference.getValue());
                notes.append("\n");
            }

            if (CuFPConstants.AchIncomeFileTransactionReference.REF_REFERENCE_TYPE_CT
                    .equals(fileTransactionReference.getType())) {
                notes.append("CONTRACT NUMBER: ");
                notes.append(fileTransactionReference.getValue());
                notes.append("\n");
            }

            if (CuFPConstants.AchIncomeFileTransactionReference.REF_REFERENCE_TYPE_VV
                    .equals(fileTransactionReference.getType())) {
                notes.append("VOUCHER NUMBER: ");
                notes.append(fileTransactionReference.getValue());
                notes.append("\n");
            }
        }

        List<AchIncomeFileTransactionDateTime> transactionDateTimes = achIncomeFileTransaction.getDateTimes();
        for (AchIncomeFileTransactionDateTime transactionDateTime : transactionDateTimes) {
            if (CuFPConstants.AchIncomeFileTransactionDateTime.DTM_DATE_TYPE_097
                    .equals(transactionDateTime.getType())) {
                notes.append("TRANSACTION CREATION: ");
                notes.append(transactionDateTime.getDateTime());
                notes.append("\n");
            }
        }

        String payerNoteText = "";
        String receiverNoteText = "";

        List<AchIncomeFileTransactionPayerOrPayeeName> payerOrPayeeNames = achIncomeFileTransaction
                .getPayerOrPayees();
        for (AchIncomeFileTransactionPayerOrPayeeName payerOrPayeeName : payerOrPayeeNames) {
            if (CuFPConstants.AchIncomeFileTransactionPayerOrPayeeName.PAYER_TYPE_PE
                    .equals(payerOrPayeeName.getType())) {
                receiverNoteText = payerOrPayeeName.getName();
                if (payerOrPayeeName.getIdCode() != null && payerOrPayeeName.getIdQualifier() != null) {
                    receiverNoteText = receiverNoteText + " " + payerOrPayeeName.getIdCode() + ":"
                            + payerOrPayeeName.getIdQualifier();
                }
            }

            if (CuFPConstants.AchIncomeFileTransactionPayerOrPayeeName.PAYER_TYPE_PR
                    .equals(payerOrPayeeName.getType())) {
                payerNoteText = payerOrPayeeName.getName();
            }
        }

        if (StringUtils.isBlank(payerNoteText) && achIncomeFileTransaction.getPremiumAdminsContact() != null) {
            payerNoteText = achIncomeFileTransaction.getPremiumAdminsContact().getName();
        }

        if (StringUtils.isBlank(receiverNoteText) && achIncomeFileTransaction.getPremiumReceiverName() != null) {
            receiverNoteText = achIncomeFileTransaction.getPremiumReceiverName().getName();
        }

        if (StringUtils.isNotBlank(receiverNoteText)) {
            notes.append("RECEIVER: ");
            notes.append(receiverNoteText);
            notes.append("\n");
        }

        if (StringUtils.isNotBlank(payerNoteText)) {
            notes.append("ORIGINATOR: ");
            notes.append(payerNoteText);
            notes.append("\n");
        }

        return notes;
    }

    private List<String> addRMRAndNTELinesToNotes(AchIncomeFileTransaction achIncomeFileTransaction,
            StringBuilder notes) {
        List<String> achNotes = new ArrayList<>();

        boolean header = true;
        Integer lineCount = 1;

        for (AchIncomeFileTransactionOpenItemReference openItemReference : achIncomeFileTransaction
                .getOpenItemReferences()) {
            int leftNoteSize = MAX_NOTE_SIZE - notes.length();
            if ((notes.length() >= MAX_NOTE_SIZE) || (leftNoteSize <= MIN_NOTE_SIZE)) {
                achNotes.add(notes.toString());
                notes = new StringBuilder();
                header = true;
            }
            String type = openItemReference.getType();
            if (CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_REFERENCE_TYPE_CR.equals(type)
                    || CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_REFERENCE_TYPE_IV.equals(type)
                    || CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_REFERENCE_TYPE_OI.equals(type)) {
                if (header) {
                    notes.append(StringUtils.rightPad("LINE",
                            CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_LINE_COLUMN_WIDTH));
                    notes.append(StringUtils.rightPad("INVOICE NUMBER",
                            CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_INVOICE_NUMBER_COLUMN_WIDTH));
                    notes.append(StringUtils.rightPad("NETAMOUNT PAID",
                            CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_NET_AMOUNT_COLUMN_WIDTH));
                    notes.append(StringUtils.rightPad("INVOICE AMOUNT",
                            CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_INVOICE_AMOUNT_COLUMN_WIDTH));
                    notes.append("\n");
                    header = false;
                }

                notes.append(StringUtils.rightPad(getFormattedAmount("00000", lineCount),
                        CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_LINE_COLUMN_WIDTH));
                notes.append(StringUtils.rightPad(openItemReference.getInvoiceNumber(),
                        CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_INVOICE_NUMBER_COLUMN_WIDTH));
                if (openItemReference.getNetAmount() != null) {
                    notes.append(StringUtils.leftPad(
                            getFormattedAmount("##,##,###.00", openItemReference.getNetAmount()),
                            CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_NET_AMOUNT_COLUMN_WIDTH));

                }
                if (openItemReference.getInvoiceAmount() != null) {
                    notes.append(StringUtils.leftPad(
                            getFormattedAmount("##,##,###.00", openItemReference.getInvoiceAmount()),
                            CuFPConstants.AchIncomeFileTransactionOpenItemReference.RMR_NOTE_INVOICE_AMOUNT_COLUMN_WIDTH));
                }
                notes.append("\n");
                lineCount++;
            }
        }

        List<AchIncomeFileTransactionNote> fileTransactionNotes = achIncomeFileTransaction.getNotes();
        for (AchIncomeFileTransactionNote fileTransactionNote : fileTransactionNotes) {
            if (fileTransactionNote.getType() != null) {
                String nteTxt = fileTransactionNote.getType().toUpperCase() + ": " + fileTransactionNote.getValue();
                int notesPlusNoteTxtLength = nteTxt.length() + notes.length();
                if (notesPlusNoteTxtLength >= MAX_NOTE_SIZE) {
                    achNotes.add(notes.toString());
                    notes = new StringBuilder();
                }
                notes.append(nteTxt.trim());
                notes.append("\n");
            }

        }
        achNotes.add(notes.toString());

        return achNotes;
    }

    private void setAchIncomeTransactionCountsAndAmounts(List<AchIncomeFileTransaction> achIncomeFileTransaction) {
        for (AchIncomeFileTransaction transaction : achIncomeFileTransaction) {
            boolean isSkippedTransaction = false;

            if (KFSConstants.GL_DEBIT_CODE.equals(transaction.getCreditDebitIndicator())) {
                isSkippedTransaction = true;
            }

            if (CuFPConstants.AchIncomeFileTransaction.TRANS_PAYMENT_METHOD_ACH
                    .equals(transaction.getPaymentMethodCode())) {
                if (isSkippedTransaction) {
                    achTotalSkippedTransactions++;
                    achTotalSkippedTransactionAmount = achTotalSkippedTransactionAmount
                            .add(transaction.getTransactionAmount());
                } else {
                    achTotalPostedTransactions++;
                    achTotalPostedTransactionAmount = achTotalPostedTransactionAmount
                            .add(transaction.getTransactionAmount());
                }
            } else if (CuFPConstants.AchIncomeFileTransaction.TRANS_PAYMENT_METHOD_FWT
                    .equals(transaction.getPaymentMethodCode())) {
                if (isSkippedTransaction) {
                    wiredTotalSkippedTransactions++;
                    wiredTotalSkippedTransactionAmount = wiredTotalSkippedTransactionAmount
                            .add(transaction.getTransactionAmount());
                } else {
                    wiredTotalPostedTransactions++;
                    wiredTotalPostedTransactionAmount = wiredTotalPostedTransactionAmount
                            .add(transaction.getTransactionAmount());
                }
            }
        }
    }

    private String getEmailMessageText(AchIncomeFile achIncomeFile, String payerMessage) {
        StringBuilder message = new StringBuilder();

        int totalPostedTransation = achTotalPostedTransactions + wiredTotalPostedTransactions;
        KualiDecimal totalPostedTransactionAmount = achTotalPostedTransactionAmount
                .add(wiredTotalPostedTransactionAmount);

        String fileDateTime;
        try {
            fileDateTime = getFormattedTimestamp(achIncomeFile, "fileDate/Time").toString();
        } catch (FormatException e) {
            // use the original file Date/Time string if encountered invalid format
            fileDateTime = achIncomeFile.getFileDate() + " " + achIncomeFile.getFileTime();
        }

        message.append("File Date: " + fileDateTime);
        message.append("\n");
        message.append("                    ");
        message.append("COUNT               ");
        message.append("        AMOUNT     ");
        message.append("\n");
        message.append(StringUtils.rightPad("ACH Posted", 20));
        message.append(StringUtils.rightPad(achTotalPostedTransactions + "", 20));
        message.append(
                StringUtils.leftPad(getFormattedAmount("##,##,##0.00", achTotalPostedTransactionAmount), 20));
        message.append("\n");
        message.append(StringUtils.rightPad("Wire Posted", 20));
        message.append(StringUtils.rightPad(wiredTotalPostedTransactions + "", 20));
        message.append(
                StringUtils.leftPad(getFormattedAmount("##,##,##0.00", wiredTotalPostedTransactionAmount), 20));
        message.append("\n");
        message.append(StringUtils.rightPad("Total Posted", 20));
        message.append(StringUtils.rightPad(totalPostedTransation + "", 20));
        message.append(StringUtils.leftPad(getFormattedAmount("##,##,##0.00", totalPostedTransactionAmount), 20));
        message.append("\n");
        message.append("\n");
        message.append(StringUtils.rightPad("ACH Skipped", 20));
        message.append(StringUtils.rightPad(achTotalSkippedTransactions + "", 20));
        message.append(
                StringUtils.leftPad(getFormattedAmount("##,##,##0.00", achTotalSkippedTransactionAmount), 20));
        message.append("\n");
        message.append(StringUtils.rightPad("Wire Skipped", 20));
        message.append(StringUtils.rightPad(wiredTotalSkippedTransactions + "", 20));
        message.append(
                StringUtils.leftPad(getFormattedAmount("##,##,##0.00", wiredTotalSkippedTransactionAmount), 20));
        message.append("\n");
        if (StringUtils.isNotBlank(payerMessage)) {
            message.append("\n");
            message.append("Transactions Missing Payer Name: ");
            message.append("\n");
            message.append(payerMessage);
        }
        return message.toString();
    }

    private Timestamp getFormattedTimestamp(AchIncomeFile achIncomeFile, String fieldName) {
        String fileDateTime = achIncomeFile.getFileDate() + achIncomeFile.getFileTime();

        // need to use 24 hour format, since otherwise exception will be thrown if the time falls in PM range.
        SimpleDateFormat dateFormat = new SimpleDateFormat(CuFPConstants.ACH_INCOME_FILE_DATE_FORMAT);
        dateFormat.setLenient(false);

        try {
            java.util.Date parsedDate = dateFormat.parse(fileDateTime);
            return new Timestamp(parsedDate.getTime());
        } catch (ParseException e) {
            throw new FormatException(
                    fieldName + " must be of the format " + CuFPConstants.ACH_INCOME_FILE_DATE_FORMAT + "\n" + e);
        }
    }

    private void copyAllMessage(Object parsedObject, PhysicalFlatFileInformation physicalFlatFileInformation) {
        List<AchIncomeFile> achIncomeFiles = (List<AchIncomeFile>) parsedObject;
        for (AchIncomeFile achIncomeFile : achIncomeFiles) {
            FlatFileInformation fileInformation = new FlatFileInformation();
            FlatFileTransactionInformation information = achIncomeFile.getFlatFileTransactionInformation();
            fileInformation.getOrAddFlatFileData(achIncomeFile.getInterchangeControlNumber(), information);
            fileInformation.addFileInfoMessage(achIncomeFile.getEmailMessageText());
            physicalFlatFileInformation.getFlatFileInfomationList().add(fileInformation);
        }
    }

    private String getFormattedAmount(String pattern, KualiDecimal amount) {
        DecimalFormat formatter = new DecimalFormat(pattern);
        return formatter.format(amount);
    }

    private String getFormattedAmount(String pattern, Integer value) {
        DecimalFormat formatter = new DecimalFormat(pattern);
        return formatter.format(value);
    }

    public void process(String fileName, Object parsedFileContents) {
        // nothing to do
    }

    public String getFileName(String principalName, Object parsedFileContents, String fileUserIdentifier) {
        return null;
    }

    protected WorkflowDocumentService getWorkflowDocumentService() {
        return KewApiServiceLocator.getWorkflowDocumentService();
    }

    public AttachmentService getAttachmentService() {
        return attachmentService;
    }

    public void setAttachmentService(AttachmentService attachmentService) {
        this.attachmentService = attachmentService;
    }

    public BankService getBankService() {
        return bankService;
    }

    public void setBankService(BankService bankService) {
        this.bankService = bankService;
    }

    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
        this.businessObjectService = businessObjectService;
    }

    public void setDocumentService(DocumentService documentService) {
        this.documentService = documentService;
    }

    public PersonService getPersonService() {
        return personService;
    }

    public void setPersonService(PersonService personService) {
        this.personService = personService;
    }

    public void setNoteService(NoteService noteService) {
        this.noteService = noteService;
    }

    public void setAttachmentsDirectory(String attachmentsDirectory) {
        this.attachmentsDirectory = attachmentsDirectory;
    }

    public void setBatchInputFileService(BatchInputFileService batchInputFileService) {
        this.batchInputFileService = batchInputFileService;
    }

    public void setBatchInputFileType(BatchInputFileType batchInputFileType) {
        this.batchInputFileType = batchInputFileType;
    }

    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    public void setEmailService(EmailService emailService) {
        this.emailService = emailService;
    }

    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

}