View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.pdp.service.impl;
17  
18  import java.sql.Timestamp;
19  import java.util.ArrayList;
20  import java.util.Calendar;
21  import java.util.Date;
22  import java.util.HashMap;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.kuali.ole.pdp.PdpConstants;
29  import org.kuali.ole.pdp.PdpKeyConstants;
30  import org.kuali.ole.pdp.PdpParameterConstants;
31  import org.kuali.ole.pdp.PdpPropertyConstants;
32  import org.kuali.ole.pdp.batch.service.ExtractPaymentService;
33  import org.kuali.ole.pdp.businessobject.AchAccountNumber;
34  import org.kuali.ole.pdp.businessobject.CustomerBank;
35  import org.kuali.ole.pdp.businessobject.CustomerProfile;
36  import org.kuali.ole.pdp.businessobject.DisbursementNumberRange;
37  import org.kuali.ole.pdp.businessobject.DisbursementType;
38  import org.kuali.ole.pdp.businessobject.FormatProcess;
39  import org.kuali.ole.pdp.businessobject.FormatProcessSummary;
40  import org.kuali.ole.pdp.businessobject.FormatSelection;
41  import org.kuali.ole.pdp.businessobject.PayeeACHAccount;
42  import org.kuali.ole.pdp.businessobject.PaymentChangeCode;
43  import org.kuali.ole.pdp.businessobject.PaymentDetail;
44  import org.kuali.ole.pdp.businessobject.PaymentGroup;
45  import org.kuali.ole.pdp.businessobject.PaymentGroupHistory;
46  import org.kuali.ole.pdp.businessobject.PaymentProcess;
47  import org.kuali.ole.pdp.businessobject.PaymentStatus;
48  import org.kuali.ole.pdp.dataaccess.FormatPaymentDao;
49  import org.kuali.ole.pdp.dataaccess.PaymentDetailDao;
50  import org.kuali.ole.pdp.dataaccess.PaymentGroupDao;
51  import org.kuali.ole.pdp.dataaccess.ProcessDao;
52  import org.kuali.ole.pdp.service.AchService;
53  import org.kuali.ole.pdp.service.FormatService;
54  import org.kuali.ole.pdp.service.PaymentGroupService;
55  import org.kuali.ole.pdp.service.PendingTransactionService;
56  import org.kuali.ole.pdp.service.impl.exception.FormatException;
57  import org.kuali.ole.select.document.service.OleSelectDocumentService;
58  import org.kuali.ole.sys.DynamicCollectionComparator;
59  import org.kuali.ole.sys.OLEConstants;
60  import org.kuali.ole.sys.OLEPropertyConstants;
61  import org.kuali.ole.sys.batch.service.SchedulerService;
62  import org.kuali.ole.sys.businessobject.Bank;
63  import org.kuali.ole.sys.context.SpringContext;
64  import org.kuali.ole.sys.service.impl.OleParameterConstants;
65  import org.kuali.rice.core.api.datetime.DateTimeService;
66  import org.kuali.rice.core.api.util.type.KualiInteger;
67  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
68  import org.kuali.rice.kim.api.identity.Person;
69  import org.kuali.rice.kim.api.identity.PersonService;
70  import org.kuali.rice.krad.service.BusinessObjectService;
71  import org.kuali.rice.krad.util.GlobalVariables;
72  import org.kuali.rice.krad.util.ObjectUtils;
73  import org.springframework.transaction.annotation.Transactional;
74  
75  @Transactional
76  public class FormatServiceImpl implements FormatService {
77      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FormatServiceImpl.class);
78  
79      protected PaymentDetailDao paymentDetailDao;
80      protected PaymentGroupDao paymentGroupDao;
81      protected ProcessDao processDao;
82      protected AchService achService;
83      protected PendingTransactionService glPendingTransactionService;
84      protected ParameterService parameterService;
85      protected FormatPaymentDao formatPaymentDao;
86      protected SchedulerService schedulerService;
87      protected BusinessObjectService businessObjectService;
88      protected PaymentGroupService paymentGroupService;
89      protected DateTimeService dateTimeService;
90      protected ExtractPaymentService extractPaymentService;
91      protected PersonService personService;
92      private OleSelectDocumentService oleSelectDocumentService;
93  
94      /**
95       * Constructs a FormatServiceImpl.java.
96       */
97      public FormatServiceImpl() {
98          super();
99      }
100 
101     /**
102      * @see org.kuali.ole.pdp.service.FormatProcessService#getDataForFormat(org.kuali.rice.kim.api.identity.Person)
103      */
104     @Override
105     public FormatSelection getDataForFormat(Person user) {
106 
107         String campusCode = user.getCampusCode();
108         Date formatStartDate = getFormatProcessStartDate(campusCode);
109 
110         // create new FormatSelection object an set the campus code and the start date
111         FormatSelection formatSelection = new FormatSelection();
112         formatSelection.setCampus(campusCode);
113         formatSelection.setStartDate(formatStartDate);
114 
115         // if format process not started yet, populate the other data as well
116         if (formatStartDate == null) {
117             formatSelection.setCustomerList(getAllCustomerProfiles());
118             formatSelection.setRangeList(getAllDisbursementNumberRanges());
119         }
120 
121         return formatSelection;
122     }
123 
124     /**
125      * @see org.kuali.ole.pdp.service.FormatService#getFormatProcessStartDate(java.lang.String)
126      */
127     @Override
128     @SuppressWarnings("rawtypes")
129     public Date getFormatProcessStartDate(String campus) {
130         LOG.debug("getFormatProcessStartDate() started");
131 
132         Map primaryKeys = new HashMap();
133         primaryKeys.put(PdpPropertyConstants.PHYS_CAMPUS_PROCESS_CODE, campus);
134         FormatProcess formatProcess = this.businessObjectService.findByPrimaryKey(FormatProcess.class, primaryKeys);
135 
136         if (formatProcess != null) {
137             LOG.debug("getFormatProcessStartDate() found");
138             return new Date(formatProcess.getBeginFormat().getTime());
139         }
140         else {
141             LOG.debug("getFormatProcessStartDate() not found");
142             return null;
143         }
144     }
145 
146     /**
147      * @see org.kuali.ole.pdp.service.FormatService#startFormatProcess(org.kuali.rice.kim.api.identity.Person, java.lang.String,
148      *      java.util.List, java.util.Date, java.lang.String)
149      */
150     @Override
151     public FormatProcessSummary startFormatProcess(Person user, String campus, List<CustomerProfile> customers, Date paydate, String paymentTypes) {
152         LOG.debug("startFormatProcess() started");
153 
154         for (CustomerProfile element : customers) {
155             if (LOG.isDebugEnabled()) {
156                 LOG.debug("startFormatProcess() Customer: " + element);
157             }
158         }
159 
160         // Create the process
161         Date d = new Date();
162         PaymentProcess paymentProcess = new PaymentProcess();
163         paymentProcess.setCampusCode(campus);
164         paymentProcess.setProcessUser(user);
165         paymentProcess.setProcessTimestamp(new Timestamp(d.getTime()));
166 
167         this.businessObjectService.save(paymentProcess);
168 
169         // add an entry in the format process table (to lock the format process)
170         FormatProcess formatProcess = new FormatProcess();
171 
172         formatProcess.setPhysicalCampusProcessCode(campus);
173         formatProcess.setBeginFormat(dateTimeService.getCurrentTimestamp());
174         formatProcess.setPaymentProcIdentifier(paymentProcess.getId().intValue());
175 
176         this.businessObjectService.save(formatProcess);
177 
178 
179         Timestamp now = new Timestamp((new Date()).getTime());
180         java.sql.Date sqlDate = new java.sql.Date(paydate.getTime());
181         Calendar c = Calendar.getInstance();
182         c.setTime(sqlDate);
183         c.set(Calendar.HOUR, 11);
184         c.set(Calendar.MINUTE, 59);
185         c.set(Calendar.SECOND, 59);
186         c.set(Calendar.MILLISECOND, 59);
187         c.set(Calendar.AM_PM, Calendar.PM);
188         Timestamp paydateTs = new Timestamp(c.getTime().getTime());
189 
190         if (LOG.isDebugEnabled()) {
191             LOG.debug("startFormatProcess() last update = " + now);
192             LOG.debug("startFormatProcess() entered paydate = " + paydate);
193             LOG.debug("startFormatProcess() actual paydate = " + paydateTs);
194         }
195         PaymentStatus format = this.businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.FORMAT);
196 
197         List customerIds = new ArrayList();
198         for (Iterator iter = customers.iterator(); iter.hasNext();) {
199             CustomerProfile element = (CustomerProfile) iter.next();
200             customerIds.add(element.getId());
201         }
202 
203         // Mark all of them ready for format
204         Iterator groupIterator = formatPaymentDao.markPaymentsForFormat(customerIds, paydateTs, paymentTypes);
205 
206         while (groupIterator.hasNext()) {
207             PaymentGroup paymentGroup = (PaymentGroup) groupIterator.next();
208             paymentGroup.setLastUpdate(paydateTs);// delete this one
209             paymentGroup.setPaymentStatus(format);
210             paymentGroup.setProcess(paymentProcess);
211             businessObjectService.save(paymentGroup);
212         }
213 
214 
215         // summarize them
216         FormatProcessSummary preFormatProcessSummary = new FormatProcessSummary();
217         Iterator<PaymentGroup> iterator = this.paymentGroupService.getByProcess(paymentProcess);
218 
219         while (iterator.hasNext()) {
220             PaymentGroup paymentGroup = iterator.next();
221             preFormatProcessSummary.add(paymentGroup);
222         }
223 
224         // if no payments found for format clear the format process
225         if (preFormatProcessSummary.getProcessSummaryList().size() == 0) {
226             LOG.debug("startFormatProcess() No payments to process.  Format process ending");
227             clearUnfinishedFormat(paymentProcess.getId().intValue());// ?? maybe call end format process
228         }
229 
230         return preFormatProcessSummary;
231     }
232 
233 
234     /**
235      * This method gets the maximum number of lines in a note.
236      *
237      * @return the maximum number of lines in a note
238      */
239     protected int getMaxNoteLines() {
240         String maxLines = parameterService.getParameterValueAsString(OleParameterConstants.PRE_DISBURSEMENT_ALL.class, PdpParameterConstants.MAX_NOTE_LINES);
241         if (StringUtils.isBlank(maxLines)) {
242             throw new RuntimeException("System parameter for max note lines is blank");
243         }
244 
245         return Integer.parseInt(maxLines);
246     }
247 
248     /**
249      * @see org.kuali.ole.pdp.service.FormatService#performFormat(java.lang.Integer)
250      */
251     @Override
252     public void performFormat(Integer processId) throws FormatException {
253         LOG.debug("performFormat() started");
254 
255         // get the PaymentProcess for the given id
256         @SuppressWarnings("rawtypes")
257         Map primaryKeys = new HashMap();
258         primaryKeys.put(PdpPropertyConstants.PaymentProcess.PAYMENT_PROCESS_ID, processId);
259         PaymentProcess paymentProcess = this.businessObjectService.findByPrimaryKey(PaymentProcess.class, primaryKeys);
260         if (paymentProcess == null) {
261             LOG.error("performFormat() Invalid proc ID " + processId);
262             throw new RuntimeException("Invalid proc ID");
263         }
264 
265         String processCampus = paymentProcess.getCampusCode();
266         FormatProcessSummary postFormatProcessSummary = new FormatProcessSummary();
267 
268         // step 1 get ACH or Check, Bank info, ACH info, sorting
269         Iterator<PaymentGroup> paymentGroupIterator = this.paymentGroupService.getByProcess(paymentProcess);
270         while (paymentGroupIterator.hasNext()) {
271             PaymentGroup paymentGroup = paymentGroupIterator.next();
272             if (LOG.isDebugEnabled()) {
273                 LOG.debug("performFormat() Step 1 Payment Group ID " + paymentGroup.getId());
274             }
275 
276             // process payment group data
277             boolean groupProcessed = processPaymentGroup(paymentGroup, paymentProcess);
278             if (!groupProcessed) {
279                 throw new FormatException("Error encountered during format");
280             }
281 
282             // save payment group
283             this.businessObjectService.save(paymentGroup);
284 
285             // Add to summary information
286             postFormatProcessSummary.add(paymentGroup);
287         }
288 
289         // step 2 assign disbursement numbers and combine checks into one if possible
290         boolean disbursementNumbersAssigned = assignDisbursementNumbersAndCombineChecks(paymentProcess, postFormatProcessSummary);
291         if (!disbursementNumbersAssigned) {
292             throw new FormatException("Error encountered during format");
293         }
294 
295         // step 3 save the summarizing info
296         LOG.debug("performFormat() Save summarizing information");
297         postFormatProcessSummary.save();
298 
299         // step 4 set formatted indicator to true and save in the db
300         paymentProcess.setFormattedIndicator(true);
301         businessObjectService.save(paymentProcess);
302 
303         // step 5 end the format process for this campus
304         LOG.debug("performFormat() End the format process for this campus");
305         endFormatProcess(processCampus);
306 
307         // step 6 tell the extract batch job to start
308         LOG.debug("performFormat() Start extract");
309         extractChecks();
310     }
311 
312     /**
313      * This method processes the payment group data.
314      *
315      * @param paymentGroup
316      * @param paymentProcess
317      */
318     protected boolean processPaymentGroup(PaymentGroup paymentGroup, PaymentProcess paymentProcess) {
319         boolean successful = true;
320 
321         paymentGroup.setSortValue(paymentGroupService.getSortGroupId(paymentGroup));
322         paymentGroup.setPhysCampusProcessCd(paymentProcess.getCampusCode());
323         paymentGroup.setProcess(paymentProcess);
324 
325         // If any one of the payment details in the group are negative, we always force a check
326         boolean noNegativeDetails = true;
327 
328         // If any one of the payment details in the group are negative, we always force a check
329         List<PaymentDetail> paymentDetailsList = paymentGroup.getPaymentDetails();
330         for (PaymentDetail paymentDetail : paymentDetailsList) {
331             if (paymentDetail.getNetPaymentAmount().doubleValue() < 0) {
332                 if (LOG.isDebugEnabled()) {
333                     LOG.debug("performFormat() Payment Group " + paymentGroup + " has payment detail net payment amount " + paymentDetail.getNetPaymentAmount());
334                     LOG.debug("performFormat() Forcing a Check for Group");
335                 }
336                 noNegativeDetails = false;
337                 break;
338             }
339         }
340 
341         // determine whether payment should be ACH or Check
342         CustomerProfile customer = paymentGroup.getBatch().getCustomerProfile();
343 
344         PayeeACHAccount payeeAchAccount = null;
345         boolean isCheck = true;
346         if (PdpConstants.PayeeIdTypeCodes.VENDOR_ID.equals(paymentGroup.getPayeeIdTypeCd()) || PdpConstants.PayeeIdTypeCodes.EMPLOYEE.equals(paymentGroup.getPayeeIdTypeCd()) || PdpConstants.PayeeIdTypeCodes.ENTITY.equals(paymentGroup.getPayeeIdTypeCd())) {
347             if (StringUtils.isNotBlank(paymentGroup.getPayeeId()) && !paymentGroup.getPymtAttachment() && !paymentGroup.getProcessImmediate() && !paymentGroup.getPymtSpecialHandling() && (customer.getAchTransactionType() != null) && noNegativeDetails) {
348                 LOG.debug("performFormat() Checking ACH");
349                 payeeAchAccount = achService.getAchInformation(paymentGroup.getPayeeIdTypeCd(), paymentGroup.getPayeeId(), customer.getAchTransactionType());
350                 isCheck = (payeeAchAccount == null);
351             }
352         }
353 
354         DisbursementType disbursementType = null;
355         if (isCheck) {
356             PaymentStatus paymentStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.PENDING_CHECK);
357             paymentGroup.setPaymentStatus(paymentStatus);
358 
359             disbursementType = businessObjectService.findBySinglePrimaryKey(DisbursementType.class, PdpConstants.DisbursementTypeCodes.CHECK);
360             paymentGroup.setDisbursementType(disbursementType);
361         }
362         else {
363             PaymentStatus paymentStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.PENDING_ACH);
364             paymentGroup.setPaymentStatus(paymentStatus);
365 
366             disbursementType = businessObjectService.findBySinglePrimaryKey(DisbursementType.class, PdpConstants.DisbursementTypeCodes.ACH);
367             paymentGroup.setDisbursementType(disbursementType);
368 
369             paymentGroup.setAchBankRoutingNbr(payeeAchAccount.getBankRoutingNumber());
370             paymentGroup.setAdviceEmailAddress(payeeAchAccount.getPayeeEmailAddress());
371             paymentGroup.setAchAccountType(payeeAchAccount.getBankAccountTypeCode());
372 
373             AchAccountNumber achAccountNumber = new AchAccountNumber();
374             achAccountNumber.setAchBankAccountNbr(payeeAchAccount.getBankAccountNumber());
375             achAccountNumber.setId(paymentGroup.getId());
376             paymentGroup.setAchAccountNumber(achAccountNumber);
377         }
378 
379         // set payment group bank
380         successful &= validateAndUpdatePaymentGroupBankCode(paymentGroup, disbursementType, customer);
381 
382         return successful;
383     }
384 
385     /**
386      * 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
387      * 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
388      * 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
389      * global message map.
390      *
391      * @param paymentGroup group to set bank on
392      * @param disbursementType type of disbursement for given payment group
393      * @param customer customer profile for payment group
394      * @return boolean true if a valid bank is set on the payment group, false otherwise
395      */
396     protected boolean validateAndUpdatePaymentGroupBankCode(PaymentGroup paymentGroup, DisbursementType disbursementType, CustomerProfile customer) {
397         boolean bankValid = true;
398 
399         String originalBankCode = paymentGroup.getBankCode();
400         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()) {
401             CustomerBank customerBank = customer.getCustomerBankByDisbursementType(disbursementType.getCode());
402             if (ObjectUtils.isNotNull(customerBank) && customerBank.isActive() && ObjectUtils.isNotNull(customerBank.getBank()) && customerBank.getBank().isActive()) {
403                 paymentGroup.setBankCode(customerBank.getBankCode());
404                 paymentGroup.setBank(customerBank.getBank());
405             }
406             else if (ObjectUtils.isNotNull(customerBank) && ObjectUtils.isNotNull(customerBank.getBank()) && ObjectUtils.isNotNull(customerBank.getBank().getContinuationBank()) && customerBank.getBank().getContinuationBank().isActive()) {
407                 paymentGroup.setBankCode(customerBank.getBank().getContinuationBank().getBankCode());
408                 paymentGroup.setBank(customerBank.getBank().getContinuationBank());
409             }
410         }
411 
412         if (ObjectUtils.isNull(paymentGroup.getBank())) {
413             LOG.error("performFormat() A bank is needed for " + disbursementType.getName() + " disbursement type for customer: " + customer);
414             GlobalVariables.getMessageMap().putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.Format.ErrorMessages.ERROR_FORMAT_BANK_MISSING, customer.getCustomerShortName());
415             bankValid = false;
416 
417             return bankValid;
418         }
419 
420         // create payment history record if bank was changed
421         if (StringUtils.isNotBlank(originalBankCode) && !paymentGroup.getBankCode().equals(originalBankCode)) {
422             PaymentGroupHistory paymentGroupHistory = new PaymentGroupHistory();
423 
424             PaymentChangeCode paymentChangeCode = businessObjectService.findBySinglePrimaryKey(PaymentChangeCode.class, PdpConstants.PaymentChangeCodes.BANK_CHNG_CD);
425             paymentGroupHistory.setPaymentChange(paymentChangeCode);
426             paymentGroupHistory.setOrigBankCode(originalBankCode);
427 
428             Bank originalBank = businessObjectService.findBySinglePrimaryKey(Bank.class, originalBankCode);
429             paymentGroupHistory.setBank(originalBank);
430             paymentGroupHistory.setOrigPaymentStatus(paymentGroup.getPaymentStatus());
431 
432             Person changeUser = getPersonService().getPersonByPrincipalName(getOleSelectDocumentService().getSelectParameterValue(OLEConstants.SYSTEM_USER));
433             paymentGroupHistory.setChangeUser(changeUser);
434             paymentGroupHistory.setPaymentGroup(paymentGroup);
435             paymentGroupHistory.setChangeTime(new Timestamp(new Date().getTime()));
436 
437             // save payment group history
438             businessObjectService.save(paymentGroupHistory);
439         }
440 
441         return bankValid;
442     }
443 
444     /**
445      * This method assigns disbursement numbers and tries to combine payment groups with disbursement type check if possible.
446      *
447      * @param paymentProcess
448      * @param postFormatProcessSummary
449      */
450     protected boolean assignDisbursementNumbersAndCombineChecks(PaymentProcess paymentProcess, FormatProcessSummary postFormatProcessSummary) {
451         boolean successful = true;
452 
453         // keep a map with paymentGroupKey and PaymentInfo (disbursementNumber, noteLines)
454         Map<String, PaymentInfo> combinedChecksMap = new HashMap<String, PaymentInfo>();
455 
456         Iterator<PaymentGroup> paymentGroupIterator = this.paymentGroupService.getByProcess(paymentProcess);
457         int maxNoteLines = getMaxNoteLines();
458 
459         while (paymentGroupIterator.hasNext()) {
460             PaymentGroup paymentGroup = paymentGroupIterator.next();
461             if (LOG.isDebugEnabled()) {
462                 LOG.debug("performFormat() Payment Group ID " + paymentGroup.getId());
463             }
464 
465             //Use the customer's profile's campus code to check for disbursement ranges
466             String campus = paymentGroup.getBatch().getCustomerProfile().getDefaultPhysicalCampusProcessingCode();
467             List<DisbursementNumberRange> disbursementRanges = paymentDetailDao.getDisbursementNumberRanges(campus);
468 
469             DisbursementNumberRange range = getRange(disbursementRanges, paymentGroup.getBank(), paymentGroup.getDisbursementType().getCode());
470 
471             if (range == null) {
472                 GlobalVariables.getMessageMap().putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.Format.ErrorMessages.ERROR_FORMAT_DISBURSEMENT_MISSING, campus, paymentGroup.getBank().getBankCode(), paymentGroup.getDisbursementType().getCode());
473                 successful = false;
474                 return successful;
475             }
476 
477             if (PdpConstants.DisbursementTypeCodes.CHECK.equals(paymentGroup.getDisbursementType().getCode())) {
478 
479                 if (paymentGroup.getPymtAttachment().booleanValue() || paymentGroup.getProcessImmediate().booleanValue() || paymentGroup.getPymtSpecialHandling().booleanValue() || (!paymentGroup.getCombineGroups())) {
480                     assignDisbursementNumber(campus, range, paymentGroup, postFormatProcessSummary);
481                 }
482                 else {
483                     String paymentGroupKey = paymentGroup.toStringKey();
484                     // check if there was another paymentGroup we can combine with
485                     if (combinedChecksMap.containsKey(paymentGroupKey)) {
486                         PaymentInfo paymentInfo = combinedChecksMap.get(paymentGroupKey);
487                         paymentInfo.noteLines = paymentInfo.noteLines.add(new KualiInteger(paymentGroup.getNoteLines()));
488 
489                         // if noteLines don't excede the maximum assign the same disbursementNumber
490                         if (paymentInfo.noteLines.intValue() <= maxNoteLines) {
491                             KualiInteger checkNumber = paymentInfo.disbursementNumber;
492                             paymentGroup.setDisbursementNbr(checkNumber);
493 
494                             // update payment info for new noteLines value
495                             combinedChecksMap.put(paymentGroupKey, paymentInfo);
496                         }
497                         // it noteLines more than maxNoteLines we remove the old entry and get a new disbursement number
498                         else {
499                             // remove old entry for this paymentGroupKey
500                             combinedChecksMap.remove(paymentGroupKey);
501 
502                             // get a new check number and the paymentGroup noteLines
503                             KualiInteger checkNumber = assignDisbursementNumber(campus, range, paymentGroup, postFormatProcessSummary);
504                             int noteLines = paymentGroup.getNoteLines();
505 
506                             // create new payment info with these two
507                             paymentInfo = new PaymentInfo(checkNumber, new KualiInteger(noteLines));
508 
509                             // add new entry in the map for this paymentGroupKey
510                             combinedChecksMap.put(paymentGroupKey, paymentInfo);
511 
512                         }
513                     }
514                     // if no entry in the map for this payment group we create a new one
515                     else {
516                         // get a new check number and the paymentGroup noteLines
517                         KualiInteger checkNumber = assignDisbursementNumber(campus, range, paymentGroup, postFormatProcessSummary);
518                         int noteLines = paymentGroup.getNoteLines();
519 
520                         // create new payment info with these two
521                         PaymentInfo paymentInfo = new PaymentInfo(checkNumber, new KualiInteger(noteLines));
522 
523                         // add new entry in the map for this paymentGroupKey
524                         combinedChecksMap.put(paymentGroupKey, paymentInfo);
525                     }
526                 }
527             }
528             else if (PdpConstants.DisbursementTypeCodes.ACH.equals(paymentGroup.getDisbursementType().getCode())) {
529                 assignDisbursementNumber(campus, range, paymentGroup, postFormatProcessSummary);
530             }
531             else {
532                 // if it isn't check or ach, we're in trouble
533                 LOG.error("assignDisbursementNumbers() Payment group " + paymentGroup.getId() + " must be CHCK or ACH.  It is: " + paymentGroup.getDisbursementType());
534                 throw new IllegalArgumentException("Payment group " + paymentGroup.getId() + " must be Check or ACH");
535             }
536 
537             this.businessObjectService.save(paymentGroup);
538 
539             // Generate a GL entry for CHCK & ACH
540             glPendingTransactionService.generatePaymentGeneralLedgerPendingEntry(paymentGroup);
541 
542             // Update all the ranges
543             LOG.debug("assignDisbursementNumbers() Save ranges");
544             for (DisbursementNumberRange element : disbursementRanges) {
545                 this.businessObjectService.save(element);
546             }
547         }
548 
549         return successful;
550     }
551 
552     /**
553      * This method gets a new disbursement number and sets it on the payment group and process summary.
554      *
555      * @param campus
556      * @param range
557      * @param paymentGroup
558      * @param postFormatProcessSummary
559      * @return
560      */
561     protected KualiInteger assignDisbursementNumber(String campus, DisbursementNumberRange range, PaymentGroup paymentGroup, FormatProcessSummary postFormatProcessSummary) {
562         KualiInteger disbursementNumber = new KualiInteger(1 + range.getLastAssignedDisbNbr().intValue());
563 
564         if (disbursementNumber.isGreaterThan(range.getEndDisbursementNbr())) {
565             GlobalVariables.getMessageMap().putError(OLEConstants.GLOBAL_ERRORS, PdpKeyConstants.Format.ErrorMessages.ERROR_FORMAT_DISBURSEMENT_EXHAUSTED, campus, paymentGroup.getBank().getBankCode(), paymentGroup.getDisbursementType().getCode());
566 
567             throw new FormatException("No more disbursement numbers for bank code " + paymentGroup.getBank().getBankCode() + " and disbursement type code " + paymentGroup.getDisbursementType().getCode());
568         }
569 
570         paymentGroup.setDisbursementNbr(disbursementNumber);
571         range.setLastAssignedDisbNbr(disbursementNumber);
572 
573         // Update the summary information
574         postFormatProcessSummary.setDisbursementNumber(paymentGroup, disbursementNumber.intValue());
575 
576         return disbursementNumber;
577     }
578 
579     /**
580      * runs the extract process.
581      */
582     protected void extractChecks() {
583         LOG.debug("extractChecks() started");
584 
585         extractPaymentService.extractChecks();
586     }
587 
588     /**
589      * @see org.kuali.ole.pdp.service.FormatService#clearUnfinishedFormat(java.lang.Integer)
590      */
591     @Override
592     @SuppressWarnings("rawtypes")
593     public void clearUnfinishedFormat(Integer processId) {
594         LOG.debug("clearUnfinishedFormat() started");
595 
596         Map primaryKeys = new HashMap();
597         primaryKeys.put(PdpPropertyConstants.PaymentProcess.PAYMENT_PROCESS_ID, processId);
598         PaymentProcess paymentProcess = this.businessObjectService.findByPrimaryKey(PaymentProcess.class, primaryKeys);
599         if (LOG.isDebugEnabled()) {
600             LOG.debug("clearUnfinishedFormat() Process: " + paymentProcess);
601         }
602 
603         Timestamp now = new Timestamp((new Date()).getTime());
604 
605         PaymentStatus openStatus = businessObjectService.findBySinglePrimaryKey(PaymentStatus.class, PdpConstants.PaymentStatusCodes.OPEN);
606 
607         Iterator groupIterator = formatPaymentDao.unmarkPaymentsForFormat(paymentProcess);
608 
609         while (groupIterator.hasNext()) {
610             PaymentGroup paymentGroup = (PaymentGroup) groupIterator.next();
611             paymentGroup.setLastUpdate(now);
612             paymentGroup.setPaymentStatus(openStatus);
613             businessObjectService.save(paymentGroup);
614         }
615 
616         endFormatProcess(paymentProcess.getCampusCode());
617     }
618 
619     /**
620      * @see org.kuali.ole.pdp.service.FormatService#resetFormatPayments(java.lang.Integer)
621      */
622     @Override
623     public void resetFormatPayments(Integer processId) {
624         LOG.debug("resetFormatPayments() started");
625         clearUnfinishedFormat(processId);
626     }
627 
628     /**
629      * @see org.kuali.ole.pdp.service.FormatService#endFormatProcess(java.lang.String)
630      */
631     @Override
632     @SuppressWarnings("rawtypes")
633     public void endFormatProcess(String campus) {
634         LOG.debug("endFormatProcess() starting");
635 
636         Map primaryKeys = new HashMap();
637         primaryKeys.put(PdpPropertyConstants.PHYS_CAMPUS_PROCESS_CODE, campus);
638 
639         this.businessObjectService.deleteMatching(FormatProcess.class, primaryKeys);
640     }
641 
642     /**
643      * @see org.kuali.ole.pdp.service.FormatService#getAllCustomerProfiles()
644      */
645     @Override
646     public List<CustomerProfile> getAllCustomerProfiles() {
647         if (LOG.isDebugEnabled()) {
648             LOG.debug("getAllCustomerProfiles() started");
649         }
650         Map<String, Object> criteria = new HashMap<String, Object>();
651         criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
652 
653         List<CustomerProfile> customerProfileList = (List<CustomerProfile>) getBusinessObjectService().findMatching(CustomerProfile.class, criteria);
654 
655         DynamicCollectionComparator.sort(customerProfileList, PdpPropertyConstants.CustomerProfile.CUSTOMER_PROFILE_CHART_CODE, PdpPropertyConstants.CustomerProfile.CUSTOMER_PROFILE_UNIT_CODE, PdpPropertyConstants.CustomerProfile.CUSTOMER_PROFILE_SUB_UNIT_CODE);
656 
657         return customerProfileList;
658     }
659 
660     /**
661      * @see org.kuali.ole.pdp.service.FormatService#getAllDisbursementNumberRanges()
662      */
663     @Override
664     public List<DisbursementNumberRange> getAllDisbursementNumberRanges() {
665         if (LOG.isDebugEnabled()) {
666             LOG.debug("getAllDisbursementNumberRanges() started");
667         }
668         Map<String, Object> criteria = new HashMap<String, Object>();
669         criteria.put(OLEPropertyConstants.ACTIVE, Boolean.TRUE);
670 
671         List<DisbursementNumberRange> disbursementNumberRangeList = (List<DisbursementNumberRange>) getBusinessObjectService().findMatching(DisbursementNumberRange.class, criteria);
672         DynamicCollectionComparator.sort(disbursementNumberRangeList, PdpPropertyConstants.DisbursementNumberRange.DISBURSEMENT_NUMBER_RANGE_PHYS_CAMPUS_PROC_CODE, PdpPropertyConstants.DisbursementNumberRange.DISBURSEMENT_NUMBER_RANGE_TYPE_CODE);
673 
674         return disbursementNumberRangeList;
675     }
676 
677     /**
678      * Given the List of disbursement number ranges for the processing campus, finds matches for the bank code and disbursement type
679      * code. If more than one match is found, the range with the latest start date (before or equal to today) will be returned.
680      *
681      * @param ranges List of disbursement ranges to search (already filtered to processing campus, active, and start date before or
682      *        equal to today)
683      * @param bank bank code to find range for
684      * @param disbursementTypeCode disbursement type code to find range for
685      * @return found <code>DisbursementNumberRange</code or null if one was not found
686      */
687     protected DisbursementNumberRange getRange(List<DisbursementNumberRange> ranges, Bank bank, String disbursementTypeCode) {
688         if (LOG.isDebugEnabled()) {
689             LOG.debug("getRange() Looking for bank = " + bank.getBankCode() + " and disbursement type " + disbursementTypeCode);
690         }
691 
692         List<DisbursementNumberRange> rangeMatches = new ArrayList<DisbursementNumberRange>();
693         for (DisbursementNumberRange range : ranges) {
694             if (range.getBank().getBankCode().equals(bank.getBankCode()) && range.getDisbursementTypeCode().equals(disbursementTypeCode)) {
695                 rangeMatches.add(range);
696             }
697         }
698 
699         // if more than one match we need to take the range with the latest start date
700         if (rangeMatches.size() > 0) {
701             DisbursementNumberRange maxStartDateRange = rangeMatches.get(0);
702             for (DisbursementNumberRange range : rangeMatches) {
703                 if (range.getDisbNbrRangeStartDt().compareTo(maxStartDateRange.getDisbNbrRangeStartDt()) > 0) {
704                     maxStartDateRange = range;
705                 }
706             }
707 
708             return maxStartDateRange;
709         }
710 
711         return null;
712     }
713 
714     /**
715      * This method sets the formatPaymentDao
716      *
717      * @param fpd
718      */
719     public void setFormatPaymentDao(FormatPaymentDao fpd) {
720         formatPaymentDao = fpd;
721     }
722 
723     /**
724      * This method sets the glPendingTransactionService
725      *
726      * @param gs
727      */
728     public void setGlPendingTransactionService(PendingTransactionService gs) {
729         glPendingTransactionService = gs;
730     }
731 
732     /**
733      * This method sets the achService
734      *
735      * @param as
736      */
737     public void setAchService(AchService as) {
738         achService = as;
739     }
740 
741     /**
742      * This method sets the processDao
743      *
744      * @param pd
745      */
746     public void setProcessDao(ProcessDao pd) {
747         processDao = pd;
748     }
749 
750     /**
751      * This method sets the paymentGroupDao
752      *
753      * @param pgd
754      */
755     public void setPaymentGroupDao(PaymentGroupDao pgd) {
756         paymentGroupDao = pgd;
757     }
758 
759     /**
760      * This method sets the paymentDetailDao
761      *
762      * @param pdd
763      */
764     public void setPaymentDetailDao(PaymentDetailDao pdd) {
765         paymentDetailDao = pdd;
766     }
767 
768     /**
769      * This method sets the schedulerService
770      *
771      * @param ss
772      */
773     public void setSchedulerService(SchedulerService ss) {
774         schedulerService = ss;
775     }
776 
777     /**
778      * This method sets the parameterService
779      *
780      * @param parameterService
781      */
782     public void setParameterService(ParameterService parameterService) {
783         this.parameterService = parameterService;
784     }
785 
786     /**
787      * Gets the businessObjectService attribute.
788      * @return Returns the businessObjectService.
789      */
790     public BusinessObjectService getBusinessObjectService() {
791         return businessObjectService;
792     }
793 
794     /**
795      * This method sets the businessObjectService
796      *
797      * @param bos
798      */
799     public void setBusinessObjectService(BusinessObjectService bos) {
800         this.businessObjectService = bos;
801     }
802 
803     /**
804      * This method sets the paymentGroupService
805      *
806      * @param paymentGroupService
807      */
808     public void setPaymentGroupService(PaymentGroupService paymentGroupService) {
809         this.paymentGroupService = paymentGroupService;
810     }
811 
812     /**
813      * This method sets the dateTimeService
814      *
815      * @param dateTimeService
816      */
817     public void setDateTimeService(DateTimeService dateTimeService) {
818         this.dateTimeService = dateTimeService;
819     }
820 
821     /**
822      * Gets the extractPaymentService attribute.
823      *
824      * @return Returns the extractPaymentService.
825      */
826     protected ExtractPaymentService getExtractPaymentService() {
827         return extractPaymentService;
828     }
829 
830     /**
831      * Sets the extractPaymentService attribute value.
832      *
833      * @param extractPaymentService The extractPaymentService to set.
834      */
835     public void setExtractPaymentService(ExtractPaymentService extractPaymentService) {
836         this.extractPaymentService = extractPaymentService;
837     }
838 
839     /**
840      * @return Returns the personService.
841      */
842     protected PersonService getPersonService() {
843         if(personService==null) {
844             personService = SpringContext.getBean(PersonService.class);
845         }
846         return personService;
847     }
848 
849     /**
850      * This class holds disbursement number and noteLines info for payment group disbursement number assignment and combine checks.
851      */
852     protected class PaymentInfo {
853         public KualiInteger disbursementNumber;
854         public KualiInteger noteLines;
855 
856         public PaymentInfo(KualiInteger disbursementNumber, KualiInteger noteLines) {
857             this.disbursementNumber = disbursementNumber;
858             this.noteLines = noteLines;
859         }
860     }
861 
862     public OleSelectDocumentService getOleSelectDocumentService() {
863         if(oleSelectDocumentService == null){
864             oleSelectDocumentService = SpringContext.getBean(OleSelectDocumentService.class);
865         }
866         return oleSelectDocumentService;
867     }
868 
869     public void setOleSelectDocumentService(OleSelectDocumentService oleSelectDocumentService) {
870         this.oleSelectDocumentService = oleSelectDocumentService;
871     }
872 
873 }