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