/* * 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); } } }