001/*
002 * Copyright 2007 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 */
016package org.kuali.ole.pdp.service.impl;
017
018import java.sql.Timestamp;
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.apache.commons.lang.StringUtils;
028import org.kuali.ole.pdp.PdpConstants;
029import org.kuali.ole.pdp.PdpKeyConstants;
030import org.kuali.ole.pdp.PdpParameterConstants;
031import org.kuali.ole.pdp.PdpPropertyConstants;
032import org.kuali.ole.pdp.batch.service.ExtractPaymentService;
033import org.kuali.ole.pdp.businessobject.AchAccountNumber;
034import org.kuali.ole.pdp.businessobject.CustomerBank;
035import org.kuali.ole.pdp.businessobject.CustomerProfile;
036import org.kuali.ole.pdp.businessobject.DisbursementNumberRange;
037import org.kuali.ole.pdp.businessobject.DisbursementType;
038import org.kuali.ole.pdp.businessobject.FormatProcess;
039import org.kuali.ole.pdp.businessobject.FormatProcessSummary;
040import org.kuali.ole.pdp.businessobject.FormatSelection;
041import org.kuali.ole.pdp.businessobject.PayeeACHAccount;
042import org.kuali.ole.pdp.businessobject.PaymentChangeCode;
043import org.kuali.ole.pdp.businessobject.PaymentDetail;
044import org.kuali.ole.pdp.businessobject.PaymentGroup;
045import org.kuali.ole.pdp.businessobject.PaymentGroupHistory;
046import org.kuali.ole.pdp.businessobject.PaymentProcess;
047import org.kuali.ole.pdp.businessobject.PaymentStatus;
048import org.kuali.ole.pdp.dataaccess.FormatPaymentDao;
049import org.kuali.ole.pdp.dataaccess.PaymentDetailDao;
050import org.kuali.ole.pdp.dataaccess.PaymentGroupDao;
051import org.kuali.ole.pdp.dataaccess.ProcessDao;
052import org.kuali.ole.pdp.service.AchService;
053import org.kuali.ole.pdp.service.FormatService;
054import org.kuali.ole.pdp.service.PaymentGroupService;
055import org.kuali.ole.pdp.service.PendingTransactionService;
056import org.kuali.ole.pdp.service.impl.exception.FormatException;
057import org.kuali.ole.select.document.service.OleSelectDocumentService;
058import org.kuali.ole.sys.DynamicCollectionComparator;
059import org.kuali.ole.sys.OLEConstants;
060import org.kuali.ole.sys.OLEPropertyConstants;
061import org.kuali.ole.sys.batch.service.SchedulerService;
062import org.kuali.ole.sys.businessobject.Bank;
063import org.kuali.ole.sys.context.SpringContext;
064import org.kuali.ole.sys.service.impl.OleParameterConstants;
065import org.kuali.rice.core.api.datetime.DateTimeService;
066import org.kuali.rice.core.api.util.type.KualiInteger;
067import org.kuali.rice.coreservice.framework.parameter.ParameterService;
068import org.kuali.rice.kim.api.identity.Person;
069import org.kuali.rice.kim.api.identity.PersonService;
070import org.kuali.rice.krad.service.BusinessObjectService;
071import org.kuali.rice.krad.util.GlobalVariables;
072import org.kuali.rice.krad.util.ObjectUtils;
073import org.springframework.transaction.annotation.Transactional;
074
075@Transactional
076public class FormatServiceImpl implements FormatService {
077    private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FormatServiceImpl.class);
078
079    protected PaymentDetailDao paymentDetailDao;
080    protected PaymentGroupDao paymentGroupDao;
081    protected ProcessDao processDao;
082    protected AchService achService;
083    protected PendingTransactionService glPendingTransactionService;
084    protected ParameterService parameterService;
085    protected FormatPaymentDao formatPaymentDao;
086    protected SchedulerService schedulerService;
087    protected BusinessObjectService businessObjectService;
088    protected PaymentGroupService paymentGroupService;
089    protected DateTimeService dateTimeService;
090    protected ExtractPaymentService extractPaymentService;
091    protected PersonService personService;
092    private OleSelectDocumentService oleSelectDocumentService;
093
094    /**
095     * Constructs a FormatServiceImpl.java.
096     */
097    public FormatServiceImpl() {
098        super();
099    }
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}