org.kuali.kfs.pdp.service.impl.FormatServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kfs.pdp.service.impl.FormatServiceImpl.java

Source

/*
 * 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.pdp.service.impl;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.mail.MessagingException;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.pdp.PdpConstants;
import org.kuali.kfs.pdp.PdpKeyConstants;
import org.kuali.kfs.pdp.PdpParameterConstants;
import org.kuali.kfs.pdp.PdpPropertyConstants;
import org.kuali.kfs.pdp.batch.service.ExtractPaymentService;
import org.kuali.kfs.pdp.businessobject.AchAccountNumber;
import org.kuali.kfs.pdp.businessobject.CustomerBank;
import org.kuali.kfs.pdp.businessobject.CustomerProfile;
import org.kuali.kfs.pdp.businessobject.DisbursementNumberRange;
import org.kuali.kfs.pdp.businessobject.DisbursementType;
import org.kuali.kfs.pdp.businessobject.FormatProcess;
import org.kuali.kfs.pdp.businessobject.FormatProcessSummary;
import org.kuali.kfs.pdp.businessobject.FormatSelection;
import org.kuali.kfs.pdp.businessobject.PayeeACHAccount;
import org.kuali.kfs.pdp.businessobject.PaymentChangeCode;
import org.kuali.kfs.pdp.businessobject.PaymentDetail;
import org.kuali.kfs.pdp.businessobject.PaymentGroup;
import org.kuali.kfs.pdp.businessobject.PaymentGroupHistory;
import org.kuali.kfs.pdp.businessobject.PaymentProcess;
import org.kuali.kfs.pdp.businessobject.PaymentStatus;
import org.kuali.kfs.pdp.dataaccess.FormatPaymentDao;
import org.kuali.kfs.pdp.dataaccess.PaymentDetailDao;
import org.kuali.kfs.pdp.dataaccess.PaymentGroupDao;
import org.kuali.kfs.pdp.dataaccess.ProcessDao;
import org.kuali.kfs.pdp.service.AchService;
import org.kuali.kfs.pdp.service.FormatService;
import org.kuali.kfs.pdp.service.PaymentGroupService;
import org.kuali.kfs.pdp.service.PendingTransactionService;
import org.kuali.kfs.pdp.service.impl.exception.FormatException;
import org.kuali.kfs.sys.DynamicCollectionComparator;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.batch.service.SchedulerService;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.kfs.sys.util.GlobalVariablesUtils;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.util.type.KualiInteger;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
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.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;

@Transactional
public class FormatServiceImpl implements FormatService {
    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FormatServiceImpl.class);

    protected PaymentDetailDao paymentDetailDao;
    protected PaymentGroupDao paymentGroupDao;
    protected ProcessDao processDao;
    protected AchService achService;
    protected PendingTransactionService glPendingTransactionService;
    protected ParameterService parameterService;
    protected FormatPaymentDao formatPaymentDao;
    protected SchedulerService schedulerService;
    protected BusinessObjectService businessObjectService;
    protected PaymentGroupService paymentGroupService;
    protected DateTimeService dateTimeService;
    protected ExtractPaymentService extractPaymentService;
    protected PersonService personService;

    /**
     * Constructs a FormatServiceImpl.java.
     */
    public FormatServiceImpl() {
        super();
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatProcessService#getDataForFormat(org.kuali.rice.kim.bo.Person)
     */
    @Override
    public FormatSelection getDataForFormat(Person user) {

        String campusCode = user.getCampusCode();
        Date formatStartDate = getFormatProcessStartDate(campusCode);

        // create new FormatSelection object an set the campus code and the start date
        FormatSelection formatSelection = new FormatSelection();
        formatSelection.setCampus(campusCode);
        formatSelection.setStartDate(formatStartDate);

        // if format process not started yet, populate the other data as well
        if (formatStartDate == null) {
            formatSelection.setCustomerList(getAllCustomerProfiles());
            formatSelection.setRangeList(getAllDisbursementNumberRanges());
        }

        return formatSelection;
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#getFormatProcessStartDate(java.lang.String)
     */
    @Override
    @SuppressWarnings("rawtypes")
    public Date getFormatProcessStartDate(String campus) {
        LOG.debug("getFormatProcessStartDate() started");

        Map primaryKeys = new HashMap();
        primaryKeys.put(PdpPropertyConstants.PHYS_CAMPUS_PROCESS_CODE, campus);
        FormatProcess formatProcess = this.businessObjectService.findByPrimaryKey(FormatProcess.class, primaryKeys);

        if (formatProcess != null) {
            LOG.debug("getFormatProcessStartDate() found");
            return new Date(formatProcess.getBeginFormat().getTime());
        } else {
            LOG.debug("getFormatProcessStartDate() not found");
            return null;
        }
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#startFormatProcess(org.kuali.rice.kim.bo.Person, java.lang.String,
     *      java.util.List, java.util.Date, java.lang.String)
     */
    @Override
    public FormatProcessSummary startFormatProcess(Person user, String campus, List<CustomerProfile> customers,
            Date paydate, String paymentTypes) {
        LOG.debug("startFormatProcess() started");

        for (CustomerProfile element : customers) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("startFormatProcess() Customer: " + element);
            }
        }

        // Create the process
        Date d = new Date();
        PaymentProcess paymentProcess = new PaymentProcess();
        paymentProcess.setCampusCode(campus);
        paymentProcess.setProcessUser(user);
        paymentProcess.setProcessTimestamp(new Timestamp(d.getTime()));

        this.businessObjectService.save(paymentProcess);

        // add an entry in the format process table (to lock the format process)
        FormatProcess formatProcess = new FormatProcess();

        formatProcess.setPhysicalCampusProcessCode(campus);
        formatProcess.setBeginFormat(dateTimeService.getCurrentTimestamp());
        formatProcess.setPaymentProcIdentifier(paymentProcess.getId().intValue());

        this.businessObjectService.save(formatProcess);

        Timestamp now = new Timestamp((new Date()).getTime());
        java.sql.Date sqlDate = new java.sql.Date(paydate.getTime());
        Calendar c = Calendar.getInstance();
        c.setTime(sqlDate);
        c.set(Calendar.HOUR, 11);
        c.set(Calendar.MINUTE, 59);
        c.set(Calendar.SECOND, 59);
        c.set(Calendar.MILLISECOND, 59);
        c.set(Calendar.AM_PM, Calendar.PM);
        Timestamp paydateTs = new Timestamp(c.getTime().getTime());

        if (LOG.isDebugEnabled()) {
            LOG.debug("startFormatProcess() last update = " + now);
            LOG.debug("startFormatProcess() entered paydate = " + paydate);
            LOG.debug("startFormatProcess() actual paydate = " + paydateTs);
        }
        PaymentStatus format = this.businessObjectService.findBySinglePrimaryKey(PaymentStatus.class,
                PdpConstants.PaymentStatusCodes.FORMAT);

        List customerIds = new ArrayList();
        for (Iterator iter = customers.iterator(); iter.hasNext();) {
            CustomerProfile element = (CustomerProfile) iter.next();
            customerIds.add(element.getId());
        }

        // Mark all of them ready for format
        Iterator groupIterator = formatPaymentDao.markPaymentsForFormat(customerIds, paydateTs, paymentTypes);

        while (groupIterator.hasNext()) {
            PaymentGroup paymentGroup = (PaymentGroup) groupIterator.next();
            paymentGroup.setLastUpdate(paydateTs);// delete this one
            paymentGroup.setPaymentStatus(format);
            paymentGroup.setProcess(paymentProcess);
            businessObjectService.save(paymentGroup);
        }

        // summarize them
        FormatProcessSummary preFormatProcessSummary = new FormatProcessSummary();
        Iterator<PaymentGroup> iterator = this.paymentGroupService.getByProcess(paymentProcess);

        while (iterator.hasNext()) {
            PaymentGroup paymentGroup = iterator.next();
            preFormatProcessSummary.add(paymentGroup);
        }

        // if no payments found for format clear the format process
        if (preFormatProcessSummary.getProcessSummaryList().size() == 0) {
            LOG.debug("startFormatProcess() No payments to process.  Format process ending");
            clearUnfinishedFormat(paymentProcess.getId().intValue());// ?? maybe call end format process
        }

        return preFormatProcessSummary;
    }

    /**
     * This method gets the maximum number of lines in a note.
     *
     * @return the maximum number of lines in a note
     */
    protected int getMaxNoteLines() {
        String maxLines = parameterService.getParameterValueAsString(
                KfsParameterConstants.PRE_DISBURSEMENT_ALL.class, PdpParameterConstants.MAX_NOTE_LINES);
        if (StringUtils.isBlank(maxLines)) {
            throw new RuntimeException("System parameter for max note lines is blank");
        }

        return Integer.parseInt(maxLines);
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#performFormat(java.lang.Integer)
     */
    @Override
    public void performFormat(Integer processId) throws FormatException {
        LOG.debug("performFormat() started");
        Person user = GlobalVariables.getUserSession().getPerson();

        // get the PaymentProcess for the given id
        @SuppressWarnings("rawtypes")
        Map primaryKeys = new HashMap();
        primaryKeys.put(PdpPropertyConstants.PaymentProcess.PAYMENT_PROCESS_ID, processId);
        PaymentProcess paymentProcess = this.businessObjectService.findByPrimaryKey(PaymentProcess.class,
                primaryKeys);
        if (paymentProcess == null) {
            LOG.error("performFormat() Invalid proc ID " + processId);
            throw new RuntimeException("Invalid proc ID");
        }

        String processCampus = paymentProcess.getCampusCode();
        FormatProcessSummary postFormatProcessSummary = new FormatProcessSummary();

        // step 1 get ACH or Check, Bank info, ACH info, sorting
        Iterator<PaymentGroup> paymentGroupIterator = this.paymentGroupService.getByProcess(paymentProcess);
        while (paymentGroupIterator.hasNext()) {
            PaymentGroup paymentGroup = paymentGroupIterator.next();
            if (LOG.isDebugEnabled()) {
                LOG.debug("performFormat() Step 1 Payment Group ID " + paymentGroup.getId());
            }

            // process payment group data
            boolean groupProcessed = processPaymentGroup(paymentGroup, paymentProcess);
            if (!groupProcessed) {
                LOG.info("Sending failure email to " + user.getEmailAddress());
                sendFailureEmail(user.getEmailAddress(), processId);
                throw new FormatException("Error encountered during format");
            }

            // save payment group
            this.businessObjectService.save(paymentGroup);

            // Add to summary information
            postFormatProcessSummary.add(paymentGroup);
        }

        // step 2 assign disbursement numbers and combine checks into one if possible
        boolean disbursementNumbersAssigned = assignDisbursementNumbersAndCombineChecks(paymentProcess,
                postFormatProcessSummary);
        if (!disbursementNumbersAssigned) {
            LOG.info("Sending failure email to " + user.getEmailAddress());
            sendFailureEmail(user.getEmailAddress(), processId);
            throw new FormatException("Error encountered during format");
        }

        // step 3 save the summarizing info
        LOG.debug("performFormat() Save summarizing information");
        postFormatProcessSummary.save();

        // step 4 set formatted indicator to true and save in the db
        paymentProcess.setFormattedIndicator(true);
        businessObjectService.save(paymentProcess);

        // step 5 end the format process for this campus
        LOG.debug("performFormat() End the format process for this campus");
        endFormatProcess(processCampus);

        // step 6 tell the extract batch job to start
        LOG.debug("performFormat() Start extract");
        extractChecks();

        LOG.info("Sending email to " + user.getEmailAddress());
        sendEmail(user.getEmailAddress(), processId);

    }

    /**
     * This method processes the payment group data.
     *
     * @param paymentGroup
     * @param paymentProcess
     */
    protected boolean processPaymentGroup(PaymentGroup paymentGroup, PaymentProcess paymentProcess) {
        boolean successful = true;

        paymentGroup.setSortValue(paymentGroupService.getSortGroupId(paymentGroup));
        paymentGroup.setPhysCampusProcessCd(paymentProcess.getCampusCode());
        paymentGroup.setProcess(paymentProcess);

        // If any one of the payment details in the group are negative, we always force a check
        boolean noNegativeDetails = true;

        // If any one of the payment details in the group are negative, we always force a check
        List<PaymentDetail> paymentDetailsList = paymentGroup.getPaymentDetails();
        for (PaymentDetail paymentDetail : paymentDetailsList) {
            if (paymentDetail.getNetPaymentAmount().doubleValue() < 0) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("performFormat() Payment Group " + paymentGroup
                            + " has payment detail net payment amount " + paymentDetail.getNetPaymentAmount());
                    LOG.debug("performFormat() Forcing a Check for Group");
                }
                noNegativeDetails = false;
                break;
            }
        }

        // determine whether payment should be ACH or Check
        CustomerProfile customer = paymentGroup.getBatch().getCustomerProfile();

        PayeeACHAccount payeeAchAccount = null;
        boolean isCheck = true;
        if (PdpConstants.PayeeIdTypeCodes.VENDOR_ID.equals(paymentGroup.getPayeeIdTypeCd())
                || PdpConstants.PayeeIdTypeCodes.EMPLOYEE.equals(paymentGroup.getPayeeIdTypeCd())
                || PdpConstants.PayeeIdTypeCodes.ENTITY.equals(paymentGroup.getPayeeIdTypeCd())) {
            if (StringUtils.isNotBlank(paymentGroup.getPayeeId()) && !paymentGroup.getPymtAttachment()
                    && !paymentGroup.getProcessImmediate() && !paymentGroup.getPymtSpecialHandling()
                    && (customer.getAchTransactionType() != null) && noNegativeDetails) {
                LOG.debug("performFormat() Checking ACH");
                payeeAchAccount = achService.getAchInformation(paymentGroup.getPayeeIdTypeCd(),
                        paymentGroup.getPayeeId(), customer.getAchTransactionType());
                isCheck = (payeeAchAccount == null);
            }
        }

        DisbursementType disbursementType = null;
        if (isCheck) {
            PaymentStatus paymentStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class,
                    PdpConstants.PaymentStatusCodes.PENDING_CHECK);
            paymentGroup.setPaymentStatus(paymentStatus);

            disbursementType = businessObjectService.findBySinglePrimaryKey(DisbursementType.class,
                    PdpConstants.DisbursementTypeCodes.CHECK);
            paymentGroup.setDisbursementType(disbursementType);
        } else {
            PaymentStatus paymentStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class,
                    PdpConstants.PaymentStatusCodes.PENDING_ACH);
            paymentGroup.setPaymentStatus(paymentStatus);

            disbursementType = businessObjectService.findBySinglePrimaryKey(DisbursementType.class,
                    PdpConstants.DisbursementTypeCodes.ACH);
            paymentGroup.setDisbursementType(disbursementType);

            paymentGroup.setAchBankRoutingNbr(payeeAchAccount.getBankRoutingNumber());
            paymentGroup.setAdviceEmailAddress(payeeAchAccount.getPayeeEmailAddress());
            paymentGroup.setAchAccountType(payeeAchAccount.getBankAccountTypeCode());

            AchAccountNumber achAccountNumber = new AchAccountNumber();
            achAccountNumber.setAchBankAccountNbr(payeeAchAccount.getBankAccountNumber());
            achAccountNumber.setId(paymentGroup.getId());
            paymentGroup.setAchAccountNumber(achAccountNumber);
        }

        // set payment group bank
        successful &= validateAndUpdatePaymentGroupBankCode(paymentGroup, disbursementType, customer);

        return successful;
    }

    /**
     * Verifies a valid bank is set on the payment group. A bank is valid if it is active and supports the given disbursement type.
     * If the payment group already has an assigned bank it will be used unless it is not valid. If the payment group bank is not
     * valid or was not given the bank specified on the customer profile to use for the given disbursement type is used. If this
     * bank is inactive then its continuation bank is used. If not valid bank to use is found an error is added to the global
     * message map.
     *
     * @param paymentGroup group to set bank on
     * @param disbursementType type of disbursement for given payment group
     * @param customer customer profile for payment group
     * @return boolean true if a valid bank is set on the payment group, false otherwise
     */
    protected boolean validateAndUpdatePaymentGroupBankCode(PaymentGroup paymentGroup,
            DisbursementType disbursementType, CustomerProfile customer) {
        boolean bankValid = true;

        String originalBankCode = paymentGroup.getBankCode();
        if (ObjectUtils.isNull(paymentGroup.getBank())
                || ((disbursementType.getCode().equals(PdpConstants.DisbursementTypeCodes.ACH)
                        && !paymentGroup.getBank().isBankAchIndicator())
                        || (disbursementType.getCode().equals(PdpConstants.DisbursementTypeCodes.CHECK)
                                && !paymentGroup.getBank().isBankCheckIndicator()))
                || !paymentGroup.getBank().isActive()) {
            CustomerBank customerBank = customer.getCustomerBankByDisbursementType(disbursementType.getCode());
            if (ObjectUtils.isNotNull(customerBank) && customerBank.isActive()
                    && ObjectUtils.isNotNull(customerBank.getBank()) && customerBank.getBank().isActive()) {
                paymentGroup.setBankCode(customerBank.getBankCode());
                paymentGroup.setBank(customerBank.getBank());
            } else if (ObjectUtils.isNotNull(customerBank) && ObjectUtils.isNotNull(customerBank.getBank())
                    && ObjectUtils.isNotNull(customerBank.getBank().getContinuationBank())
                    && customerBank.getBank().getContinuationBank().isActive()) {
                paymentGroup.setBankCode(customerBank.getBank().getContinuationBank().getBankCode());
                paymentGroup.setBank(customerBank.getBank().getContinuationBank());
            }
        }

        if (ObjectUtils.isNull(paymentGroup.getBank())) {
            LOG.error("performFormat() A bank is needed for " + disbursementType.getName()
                    + " disbursement type for customer: " + customer);
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS,
                    PdpKeyConstants.Format.ErrorMessages.ERROR_FORMAT_BANK_MISSING,
                    customer.getCustomerShortName());
            bankValid = false;

            return bankValid;
        }

        // create payment history record if bank was changed
        if (StringUtils.isNotBlank(originalBankCode) && !paymentGroup.getBankCode().equals(originalBankCode)) {
            PaymentGroupHistory paymentGroupHistory = new PaymentGroupHistory();

            PaymentChangeCode paymentChangeCode = businessObjectService
                    .findBySinglePrimaryKey(PaymentChangeCode.class, PdpConstants.PaymentChangeCodes.BANK_CHNG_CD);
            paymentGroupHistory.setPaymentChange(paymentChangeCode);
            paymentGroupHistory.setOrigBankCode(originalBankCode);

            Bank originalBank = businessObjectService.findBySinglePrimaryKey(Bank.class, originalBankCode);
            paymentGroupHistory.setBank(originalBank);
            paymentGroupHistory.setOrigPaymentStatus(paymentGroup.getPaymentStatus());

            Person changeUser = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER);
            paymentGroupHistory.setChangeUser(changeUser);
            paymentGroupHistory.setPaymentGroup(paymentGroup);
            paymentGroupHistory.setChangeTime(new Timestamp(new Date().getTime()));

            // save payment group history
            businessObjectService.save(paymentGroupHistory);
        }

        return bankValid;
    }

    /**
     * This method assigns disbursement numbers and tries to combine payment groups with disbursement type check if possible.
     *
     * @param paymentProcess
     * @param postFormatProcessSummary
     */
    protected boolean assignDisbursementNumbersAndCombineChecks(PaymentProcess paymentProcess,
            FormatProcessSummary postFormatProcessSummary) {
        boolean successful = true;

        // keep a map with paymentGroupKey and PaymentInfo (disbursementNumber, noteLines)
        Map<String, PaymentInfo> combinedChecksMap = new HashMap<String, PaymentInfo>();

        Iterator<PaymentGroup> paymentGroupIterator = this.paymentGroupService.getByProcess(paymentProcess);
        int maxNoteLines = getMaxNoteLines();

        while (paymentGroupIterator.hasNext()) {
            PaymentGroup paymentGroup = paymentGroupIterator.next();
            if (LOG.isDebugEnabled()) {
                LOG.debug("performFormat() Payment Group ID " + paymentGroup.getId());
            }

            // Use the customer's profile's campus code to check for disbursement ranges
            String campus = paymentGroup.getBatch().getCustomerProfile().getDefaultPhysicalCampusProcessingCode();
            List<DisbursementNumberRange> disbursementRanges = paymentDetailDao.getDisbursementNumberRanges(campus);

            DisbursementNumberRange range = getRange(disbursementRanges, paymentGroup.getBank(),
                    paymentGroup.getDisbursementType().getCode());

            if (range == null) {
                GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS,
                        PdpKeyConstants.Format.ErrorMessages.ERROR_FORMAT_DISBURSEMENT_MISSING, campus,
                        paymentGroup.getBank().getBankCode(), paymentGroup.getDisbursementType().getCode());
                successful = false;
                return successful;
            }

            if (PdpConstants.DisbursementTypeCodes.CHECK.equals(paymentGroup.getDisbursementType().getCode())) {

                if (paymentGroup.getPymtAttachment().booleanValue()
                        || paymentGroup.getProcessImmediate().booleanValue()
                        || paymentGroup.getPymtSpecialHandling().booleanValue()
                        || (!paymentGroup.getCombineGroups())) {
                    assignDisbursementNumber(campus, range, paymentGroup, postFormatProcessSummary);
                } else {
                    String paymentGroupKey = paymentGroup.toStringKey();
                    // check if there was another paymentGroup we can combine with
                    if (combinedChecksMap.containsKey(paymentGroupKey)) {
                        PaymentInfo paymentInfo = combinedChecksMap.get(paymentGroupKey);
                        paymentInfo.noteLines = paymentInfo.noteLines
                                .add(new KualiInteger(paymentGroup.getNoteLines()));

                        // if noteLines don't excede the maximum assign the same disbursementNumber
                        if (paymentInfo.noteLines.intValue() <= maxNoteLines) {
                            KualiInteger checkNumber = paymentInfo.disbursementNumber;
                            paymentGroup.setDisbursementNbr(checkNumber);

                            // update payment info for new noteLines value
                            combinedChecksMap.put(paymentGroupKey, paymentInfo);
                        }
                        // it noteLines more than maxNoteLines we remove the old entry and get a new disbursement number
                        else {
                            // remove old entry for this paymentGroupKey
                            combinedChecksMap.remove(paymentGroupKey);

                            // get a new check number and the paymentGroup noteLines
                            KualiInteger checkNumber = assignDisbursementNumber(campus, range, paymentGroup,
                                    postFormatProcessSummary);
                            int noteLines = paymentGroup.getNoteLines();

                            // create new payment info with these two
                            paymentInfo = new PaymentInfo(checkNumber, new KualiInteger(noteLines));

                            // add new entry in the map for this paymentGroupKey
                            combinedChecksMap.put(paymentGroupKey, paymentInfo);

                        }
                    }
                    // if no entry in the map for this payment group we create a new one
                    else {
                        // get a new check number and the paymentGroup noteLines
                        KualiInteger checkNumber = assignDisbursementNumber(campus, range, paymentGroup,
                                postFormatProcessSummary);
                        int noteLines = paymentGroup.getNoteLines();

                        // create new payment info with these two
                        PaymentInfo paymentInfo = new PaymentInfo(checkNumber, new KualiInteger(noteLines));

                        // add new entry in the map for this paymentGroupKey
                        combinedChecksMap.put(paymentGroupKey, paymentInfo);
                    }
                }
            } else if (PdpConstants.DisbursementTypeCodes.ACH
                    .equals(paymentGroup.getDisbursementType().getCode())) {
                assignDisbursementNumber(campus, range, paymentGroup, postFormatProcessSummary);
            } else {
                // if it isn't check or ach, we're in trouble
                LOG.error("assignDisbursementNumbers() Payment group " + paymentGroup.getId()
                        + " must be CHCK or ACH.  It is: " + paymentGroup.getDisbursementType());
                GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS,
                        "assignDisbursementNumbers() Payment group " + paymentGroup.getId()
                                + " must be CHCK or ACH.  It is: " + paymentGroup.getDisbursementType());
                throw new IllegalArgumentException(
                        "Payment group " + paymentGroup.getId() + " must be Check or ACH");
            }

            this.businessObjectService.save(paymentGroup);

            // Generate a GL entry for CHCK & ACH
            glPendingTransactionService.generatePaymentGeneralLedgerPendingEntry(paymentGroup);

            // Update all the ranges
            LOG.debug("assignDisbursementNumbers() Save ranges");
            for (DisbursementNumberRange element : disbursementRanges) {
                this.businessObjectService.save(element);
            }
        }

        return successful;
    }

    /**
     * This method gets a new disbursement number and sets it on the payment group and process summary.
     *
     * @param campus
     * @param range
     * @param paymentGroup
     * @param postFormatProcessSummary
     * @return
     */
    protected KualiInteger assignDisbursementNumber(String campus, DisbursementNumberRange range,
            PaymentGroup paymentGroup, FormatProcessSummary postFormatProcessSummary) {
        KualiInteger disbursementNumber = new KualiInteger(1 + range.getLastAssignedDisbNbr().intValue());

        if (disbursementNumber.isGreaterThan(range.getEndDisbursementNbr())) {
            GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS,
                    PdpKeyConstants.Format.ErrorMessages.ERROR_FORMAT_DISBURSEMENT_EXHAUSTED, campus,
                    paymentGroup.getBank().getBankCode(), paymentGroup.getDisbursementType().getCode());

            throw new FormatException(
                    "No more disbursement numbers for bank code " + paymentGroup.getBank().getBankCode()
                            + " and disbursement type code " + paymentGroup.getDisbursementType().getCode());
        }

        paymentGroup.setDisbursementNbr(disbursementNumber);
        range.setLastAssignedDisbNbr(disbursementNumber);

        // Update the summary information
        postFormatProcessSummary.setDisbursementNumber(paymentGroup, disbursementNumber.intValue());

        return disbursementNumber;
    }

    /**
     * runs the extract process.
     */
    protected void extractChecks() {
        LOG.debug("extractChecks() started");

        extractPaymentService.extractChecks();
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#clearUnfinishedFormat(java.lang.Integer)
     */
    @Override
    @SuppressWarnings("rawtypes")
    public void clearUnfinishedFormat(Integer processId) {
        LOG.debug("clearUnfinishedFormat() started");

        Map primaryKeys = new HashMap();
        primaryKeys.put(PdpPropertyConstants.PaymentProcess.PAYMENT_PROCESS_ID, processId);
        PaymentProcess paymentProcess = this.businessObjectService.findByPrimaryKey(PaymentProcess.class,
                primaryKeys);
        if (LOG.isDebugEnabled()) {
            LOG.debug("clearUnfinishedFormat() Process: " + paymentProcess);
        }

        Timestamp now = new Timestamp((new Date()).getTime());

        PaymentStatus openStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class,
                PdpConstants.PaymentStatusCodes.OPEN);

        Iterator groupIterator = formatPaymentDao.unmarkPaymentsForFormat(paymentProcess);

        while (groupIterator.hasNext()) {
            PaymentGroup paymentGroup = (PaymentGroup) groupIterator.next();
            paymentGroup.setLastUpdate(now);
            paymentGroup.setPaymentStatus(openStatus);
            businessObjectService.save(paymentGroup);
        }

        endFormatProcess(paymentProcess.getCampusCode());
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#resetFormatPayments(java.lang.Integer)
     */
    @Override
    public void resetFormatPayments(Integer processId) {
        LOG.debug("resetFormatPayments() started");
        clearUnfinishedFormat(processId);
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#endFormatProcess(java.lang.String)
     */
    @Override
    @SuppressWarnings("rawtypes")
    public void endFormatProcess(String campus) {
        LOG.debug("endFormatProcess() starting");

        Map primaryKeys = new HashMap();
        primaryKeys.put(PdpPropertyConstants.PHYS_CAMPUS_PROCESS_CODE, campus);

        this.businessObjectService.deleteMatching(FormatProcess.class, primaryKeys);
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#getAllCustomerProfiles()
     */
    @Override
    public List<CustomerProfile> getAllCustomerProfiles() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getAllCustomerProfiles() started");
        }
        Map<String, Object> criteria = new HashMap<String, Object>();
        criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);

        List<CustomerProfile> customerProfileList = (List<CustomerProfile>) getBusinessObjectService()
                .findMatching(CustomerProfile.class, criteria);

        DynamicCollectionComparator.sort(customerProfileList,
                PdpPropertyConstants.CustomerProfile.CUSTOMER_PROFILE_CHART_CODE,
                PdpPropertyConstants.CustomerProfile.CUSTOMER_PROFILE_UNIT_CODE,
                PdpPropertyConstants.CustomerProfile.CUSTOMER_PROFILE_SUB_UNIT_CODE);

        return customerProfileList;
    }

    /**
     * @see org.kuali.kfs.pdp.service.FormatService#getAllDisbursementNumberRanges()
     */
    @Override
    public List<DisbursementNumberRange> getAllDisbursementNumberRanges() {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getAllDisbursementNumberRanges() started");
        }
        Map<String, Object> criteria = new HashMap<String, Object>();
        criteria.put(KFSPropertyConstants.ACTIVE, Boolean.TRUE);

        List<DisbursementNumberRange> disbursementNumberRangeList = (List<DisbursementNumberRange>) getBusinessObjectService()
                .findMatching(DisbursementNumberRange.class, criteria);
        DynamicCollectionComparator.sort(disbursementNumberRangeList,
                PdpPropertyConstants.DisbursementNumberRange.DISBURSEMENT_NUMBER_RANGE_PHYS_CAMPUS_PROC_CODE,
                PdpPropertyConstants.DisbursementNumberRange.DISBURSEMENT_NUMBER_RANGE_TYPE_CODE);

        return disbursementNumberRangeList;
    }

    /**
     * Given the List of disbursement number ranges for the processing campus, finds matches for the bank code and disbursement type
     * code. If more than one match is found, the range with the latest start date (before or equal to today) will be returned.
     *
     * @param ranges List of disbursement ranges to search (already filtered to processing campus, active, and start date before or
     *        equal to today)
     * @param bank bank code to find range for
     * @param disbursementTypeCode disbursement type code to find range for
     * @return found <code>DisbursementNumberRange</code or null if one was not found
     */
    protected DisbursementNumberRange getRange(List<DisbursementNumberRange> ranges, Bank bank,
            String disbursementTypeCode) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getRange() Looking for bank = " + bank.getBankCode() + " and disbursement type "
                    + disbursementTypeCode);
        }

        List<DisbursementNumberRange> rangeMatches = new ArrayList<DisbursementNumberRange>();
        for (DisbursementNumberRange range : ranges) {
            if (range.getBank().getBankCode().equals(bank.getBankCode())
                    && range.getDisbursementTypeCode().equals(disbursementTypeCode)) {
                rangeMatches.add(range);
            }
        }

        // if more than one match we need to take the range with the latest start date
        if (rangeMatches.size() > 0) {
            DisbursementNumberRange maxStartDateRange = rangeMatches.get(0);
            for (DisbursementNumberRange range : rangeMatches) {
                if (range.getDisbNbrRangeStartDt().compareTo(maxStartDateRange.getDisbNbrRangeStartDt()) > 0) {
                    maxStartDateRange = range;
                }
            }

            return maxStartDateRange;
        }

        return null;
    }

    /**
     * This method sets the formatPaymentDao
     *
     * @param fpd
     */
    public void setFormatPaymentDao(FormatPaymentDao fpd) {
        formatPaymentDao = fpd;
    }

    /**
     * This method sets the glPendingTransactionService
     *
     * @param gs
     */
    public void setGlPendingTransactionService(PendingTransactionService gs) {
        glPendingTransactionService = gs;
    }

    /**
     * This method sets the achService
     *
     * @param as
     */
    public void setAchService(AchService as) {
        achService = as;
    }

    /**
     * This method sets the processDao
     *
     * @param pd
     */
    public void setProcessDao(ProcessDao pd) {
        processDao = pd;
    }

    /**
     * This method sets the paymentGroupDao
     *
     * @param pgd
     */
    public void setPaymentGroupDao(PaymentGroupDao pgd) {
        paymentGroupDao = pgd;
    }

    /**
     * This method sets the paymentDetailDao
     *
     * @param pdd
     */
    public void setPaymentDetailDao(PaymentDetailDao pdd) {
        paymentDetailDao = pdd;
    }

    /**
     * This method sets the schedulerService
     *
     * @param ss
     */
    public void setSchedulerService(SchedulerService ss) {
        schedulerService = ss;
    }

    /**
     * This method sets the parameterService
     *
     * @param parameterService
     */
    public void setParameterService(ParameterService parameterService) {
        this.parameterService = parameterService;
    }

    /**
     * Gets the businessObjectService attribute.
     *
     * @return Returns the businessObjectService.
     */
    public BusinessObjectService getBusinessObjectService() {
        return businessObjectService;
    }

    /**
     * This method sets the businessObjectService
     *
     * @param bos
     */
    public void setBusinessObjectService(BusinessObjectService bos) {
        this.businessObjectService = bos;
    }

    /**
     * This method sets the paymentGroupService
     *
     * @param paymentGroupService
     */
    public void setPaymentGroupService(PaymentGroupService paymentGroupService) {
        this.paymentGroupService = paymentGroupService;
    }

    /**
     * This method sets the dateTimeService
     *
     * @param dateTimeService
     */
    public void setDateTimeService(DateTimeService dateTimeService) {
        this.dateTimeService = dateTimeService;
    }

    /**
     * Gets the extractPaymentService attribute.
     *
     * @return Returns the extractPaymentService.
     */
    protected ExtractPaymentService getExtractPaymentService() {
        return extractPaymentService;
    }

    /**
     * Sets the extractPaymentService attribute value.
     *
     * @param extractPaymentService The extractPaymentService to set.
     */
    public void setExtractPaymentService(ExtractPaymentService extractPaymentService) {
        this.extractPaymentService = extractPaymentService;
    }

    /**
     * @return Returns the personService.
     */
    protected PersonService getPersonService() {
        if (personService == null) {
            personService = SpringContext.getBean(PersonService.class);
        }
        return personService;
    }

    /**
     * This class holds disbursement number and noteLines info for payment group disbursement number assignment and combine checks.
     */
    protected class PaymentInfo {
        public KualiInteger disbursementNumber;
        public KualiInteger noteLines;

        public PaymentInfo(KualiInteger disbursementNumber, KualiInteger noteLines) {
            this.disbursementNumber = disbursementNumber;
            this.noteLines = noteLines;
        }
    }

    protected void sendEmail(String toAddress, int processId) {
        MailMessage message = new MailMessage();
        message.setFromAddress(SpringContext.getBean(ParameterService.class).getParameterValueAsString(
                KFSConstants.CoreModuleNamespaces.PDP, KfsParameterConstants.BATCH_COMPONENT,
                KFSConstants.FROM_EMAIL_ADDRESS_PARM_NM));
        message.setSubject("PDP Format Completed for Process ID " + processId);
        message.setToAddresses(new HashSet());
        message.addToAddress(toAddress);
        message.setMessage("The PDP format for Process ID " + processId
                + " is complete. Please access the PDP Format Summary lookup for the final disbursement numbers and amounts");
        try {
            SpringContext.getBean(MailService.class).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 void sendFailureEmail(String toAddress, int processId) {
        MailMessage message = new MailMessage();
        message.setFromAddress(SpringContext.getBean(ParameterService.class).getParameterValueAsString(
                KFSConstants.CoreModuleNamespaces.PDP, KfsParameterConstants.BATCH_COMPONENT,
                KFSConstants.FROM_EMAIL_ADDRESS_PARM_NM));
        message.setSubject("PDP Format Failed for Process ID " + processId);
        message.setToAddresses(new HashSet());
        message.addToAddress(toAddress);
        StringBuffer msg = new StringBuffer(
                "The PDP format for Process ID " + processId + " has failed. It returned following errors.\n\n");
        List<String> errList = GlobalVariablesUtils.extractGlobalVariableErrors();
        for (String err : errList) {
            msg.append(err + "\n");
        }
        message.setMessage(msg.toString());
        try {
            SpringContext.getBean(MailService.class).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);
        }
    }

}