001    /**
002     * Copyright 2004-2013 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.hr.lm.accrual.service;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.sql.Timestamp;
021    import java.util.ArrayList;
022    import java.util.Calendar;
023    import java.util.GregorianCalendar;
024    import java.util.HashMap;
025    import java.util.HashSet;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    
030    import org.apache.commons.collections.CollectionUtils;
031    import org.apache.commons.lang.StringUtils;
032    import org.joda.time.DateTime;
033    import org.joda.time.Interval;
034    import org.kuali.hr.job.Job;
035    import org.kuali.hr.lm.LMConstants;
036    import org.kuali.hr.lm.accrual.AccrualCategory;
037    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
038    import org.kuali.hr.lm.accrual.PrincipalAccrualRan;
039    import org.kuali.hr.lm.accrual.RateRange;
040    import org.kuali.hr.lm.accrual.RateRangeAggregate;
041    import org.kuali.hr.lm.leaveblock.LeaveBlock;
042    import org.kuali.hr.lm.leaveplan.LeavePlan;
043    import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
044    import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
045    import org.kuali.hr.time.assignment.Assignment;
046    import org.kuali.hr.time.calendar.CalendarEntries;
047    import org.kuali.hr.time.earncode.EarnCode;
048    import org.kuali.hr.time.principal.PrincipalHRAttributes;
049    import org.kuali.hr.time.service.base.TkServiceLocator;
050    import org.kuali.hr.time.util.TKContext;
051    import org.kuali.hr.time.util.TKUtils;
052    import org.kuali.hr.time.util.TkConstants;
053    
054    import edu.emory.mathcs.backport.java.util.Collections;
055    
056    public class AccrualServiceImpl implements AccrualService {
057    
058            @Override
059            public void runAccrual(String principalId) {
060                    Date startDate = getStartAccrualDate(principalId);
061                    Date endDate = getEndAccrualDate(principalId);
062    
063                    System.out.println("AccrualServiceImpl.runAccrual() STARTED with Principal: "+principalId);
064                    
065                    runAccrual(principalId,startDate,endDate, true);
066                    
067            }
068            
069            @Override
070            public void runAccrual(String principalId, Date startDate, Date endDate, boolean recordRanData) {
071                    runAccrual(principalId, startDate, endDate, recordRanData, TKContext.getPrincipalId());
072            }
073    
074            @SuppressWarnings("unchecked")
075            @Override
076            public void runAccrual(String principalId, Date startDate, Date endDate, boolean recordRanData, String runAsPrincipalId) {
077                    List<LeaveBlock> accrualLeaveBlocks = new ArrayList<LeaveBlock>();
078                    Map<String, BigDecimal> accumulatedAccrualCatToAccrualAmounts = new HashMap<String,BigDecimal>();
079                    Map<String, BigDecimal> accumulatedAccrualCatToNegativeAccrualAmounts = new HashMap<String,BigDecimal>();
080                    
081                    if (startDate != null && endDate != null) {
082                            System.out.println("AccrualServiceImpl.runAccrual() STARTED with Principal: "+principalId+" Start: "+startDate.toString()+" End: "+endDate.toString());
083                    }
084                    if(startDate.after(endDate)) {
085                            throw new RuntimeException("Start Date " + startDate.toString() + " should not be later than End Date " + endDate.toString());
086                    }
087                    //Inactivate all previous accrual-generated entries for this span of time
088                    deactivateOldAccruals(principalId, startDate, endDate, runAsPrincipalId);
089                    
090                    //Build a rate range aggregate with appropriate information for this period of time detailing Rate Ranges for job
091                    //entries for this range of time
092                    RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate); 
093                    PrincipalHRAttributes phra = null;
094                    PrincipalHRAttributes endPhra = null;
095                    LeavePlan lp = null;
096                    List<AccrualCategory> accrCatList = null;
097                    
098                    //Iterate over every day in span 
099                    Calendar aCal = Calendar.getInstance();
100                    aCal.setTime(startDate);
101                    while (!aCal.getTime().after(endDate)) {
102                            java.util.Date currentDate = aCal.getTime();
103                            RateRange currentRange = rrAggregate.getRateOnDate(currentDate);
104                            if(currentRange == null) {
105                                    aCal.add(Calendar.DATE, 1);
106                                    continue;
107                            }
108                            
109                            phra = currentRange.getPrincipalHRAttributes();
110                            if(phra == null || TKUtils.removeTime(currentDate).before(TKUtils.removeTime(phra.getServiceDate()))) {
111                                    aCal.add(Calendar.DATE, 1);
112                                    continue;
113                            }
114                            
115                            
116            // use the effectiveDate of this principalHRAttribute to search for inactive entries for this principalId
117            // If there's an inactive entry, it means the job is going to end on the effectiveDate of the inactive entry
118            // used for minimumPercentage and proration
119                            endPhra = currentRange.getEndPrincipalHRAttributes();
120                            if(endPhra != null && TKUtils.removeTime(currentDate).after(TKUtils.removeTime(endPhra.getEffectiveDate()))) {
121                                    aCal.add(Calendar.DATE, 1);
122                                    continue;
123                            }
124                            
125            // if the date range is before the service date of this employee, do not calculate accrual
126                            if(endDate.before(phra.getServiceDate())) {
127                                    return;
128                            }
129                            lp = currentRange.getLeavePlan();
130                            accrCatList = currentRange.getAcList();
131                            // if the employee status is changed, create an empty leave block on the currentDate
132                            if(currentRange.isStatusChanged()) {
133                                    this.createEmptyLeaveBlockForStatusChange(principalId, accrualLeaveBlocks, currentDate);
134                            }
135                            // if no job found for the employee on the currentDate, do nothing
136                            if(CollectionUtils.isEmpty(currentRange.getJobs())) {
137                                    aCal.add(Calendar.DATE, 1);
138                                    continue;
139                            }
140                            
141                            BigDecimal ftePercentage = currentRange.getAccrualRatePercentageModifier();
142                            BigDecimal totalOfStandardHours = currentRange.getStandardHours();
143                            boolean fullFteGranted = false;
144                            for(AccrualCategory anAC : accrCatList) {
145                                    fullFteGranted = false;
146                                    if(!currentDate.before(phra.getEffectiveDate()) && !anAC.getAccrualEarnInterval().equals("N")) {        // "N" means no accrual
147                                            boolean prorationFlag = this.getProrationFlag(anAC.getProration());
148                                            // get the accrual rule 
149                                            AccrualCategoryRule currentAcRule = this.getRuleForAccrualCategory(currentRange.getAcRuleList(), anAC);
150                                    
151                                            // check if accrual category rule changed
152                                            if(currentAcRule != null) {
153                                                    java.util.Date ruleStartDate = getRuleStartDate(currentAcRule.getServiceUnitOfTime(), phra.getServiceDate(), currentAcRule.getStart());
154                                                    Date ruleStartSqlDate = new java.sql.Date(ruleStartDate.getTime());
155                                                    java.util.Date previousIntervalDay = this.getPrevIntervalDate(ruleStartSqlDate, anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
156                                                    java.util.Date nextIntervalDay = this.getNextIntervalDate(ruleStartSqlDate, anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
157                                                    
158                                                    RateRange previousRange = rrAggregate.getRateOnDate(previousIntervalDay);
159                                                    AccrualCategoryRule previousAcRule = null;
160                                                    if(previousRange != null) {
161                                                            previousAcRule = this.getRuleForAccrualCategory(previousRange.getAcRuleList(), anAC);
162                                                    }
163                                                    // rule changed
164                                                    if(previousAcRule != null && !previousAcRule.getLmAccrualCategoryRuleId().equals(currentAcRule.getLmAccrualCategoryRuleId())) {
165                                                            if(TKUtils.removeTime(currentDate).compareTo(TKUtils.removeTime(previousIntervalDay)) >= 0 
166                                                                            && TKUtils.removeTime(currentDate).compareTo(TKUtils.removeTime(nextIntervalDay)) <= 0) {
167                                                                    int workDaysInBetween = TKUtils.getWorkDays(ruleStartSqlDate, nextIntervalDay);
168                                                                    boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(), 
169                                                                                                    anAC.getAccrualEarnInterval(), workDaysInBetween, new java.sql.Date(nextIntervalDay.getTime()),
170                                                                                                    phra.getPayCalendar(), rrAggregate.getCalEntryMap());
171                                                                    if(prorationFlag) {
172                                                                            if(minReachedFlag) {
173                                                                                    // min reached, proration=true, rule changed, then use actual work days of currentRule for calculation
174                                                                                    // so nothing special needs to be done here                                                             
175                                                                            } else {
176                                                                                    //minimum percentage NOT reached, proration = true, rule changed, then use previousRule for the whole pay period
177                                                                                    currentAcRule = previousAcRule;
178                                                                            }
179                                                                    } else {
180                                                                            if(minReachedFlag) {
181                                                                                    // min reached, proration = false, rule changed, then accrual the whole fte of the new rule for this pay interval
182                                                                                    accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), currentAcRule.getAccrualRate());
183                                                                                    fullFteGranted = true;
184                                                                            } else {
185                                                                                    //min NOT reached, proration = false, rule changed, then accrual the whole fte of the previous rule for this pay interval
186                                                                                    accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), previousAcRule.getAccrualRate());
187                                                                                    fullFteGranted = true;
188                                                                            }
189                                                                    }
190                                                            }
191                                                    }
192                                            }
193                                            
194                                            // check for first pay period of principal attributes considering minimum percentage and proration      
195                                            java.util.Date firstIntervalDate = this.getNextIntervalDate(phra.getEffectiveDate(), anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
196                                            if(!TKUtils.removeTime(currentDate).before(TKUtils.removeTime(phra.getEffectiveDate())) 
197                                                            && !TKUtils.removeTime(currentDate).after(TKUtils.removeTime(firstIntervalDate))) {
198                                                    int workDaysInBetween = TKUtils.getWorkDays(phra.getEffectiveDate(), firstIntervalDate);
199                                                    boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(),  anAC.getAccrualEarnInterval(), 
200                                                                            workDaysInBetween, new java.sql.Date(firstIntervalDate.getTime()),
201                                                                            phra.getPayCalendar(), rrAggregate.getCalEntryMap());
202                                                    
203                                                    if(prorationFlag) {
204                                                            if(minReachedFlag) {
205                                                                    // minimum reached, proration = true, first pay period, then use actual work days of currentRule for calculation
206                                                                    // so nothing special needs to be done here
207                                                            } else {
208                                                                    // min NOT reached, proration = true, first pay period, then no accrual for this pay period
209                                                                    accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
210                                                                    accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
211                                                                    continue;
212                                                            }
213                                                    } else {
214                                                            if(minReachedFlag) {
215                                                                    //  minimum reached, proration = false, first pay period, then accrual the whole fte of current AC rule for this pay interval
216                                                                    accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), currentAcRule.getAccrualRate());
217                                                                    fullFteGranted = true;
218                                                            } else {
219                                                                    // min NOT reached, proration = false, first pay period, then no accrual for this pay period
220                                                                    accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
221                                                                    accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
222                                                                    continue;
223                                                            }
224                                                    }
225                                            }
226                                            // last accrual interval
227                                            if(endPhra != null) {   // the employment is going to end on the effectiveDate of enPhra
228                                                    java.util.Date previousIntervalDate = this.getPrevIntervalDate(endPhra.getEffectiveDate(), anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
229                                                    // currentDate is between the end date and the last interval date, so we are in the last interval
230                                                    if(!TKUtils.removeTime(currentDate).after(TKUtils.removeTime(endPhra.getEffectiveDate())) 
231                                                                    && TKUtils.removeTime(currentDate).after(TKUtils.removeTime(previousIntervalDate))) {
232                                                            java.util.Date lastIntervalDate = this.getNextIntervalDate(endPhra.getEffectiveDate(), anAC.getAccrualEarnInterval(),  phra.getPayCalendar(), rrAggregate.getCalEntryMap());
233                                                            int workDaysInBetween = TKUtils.getWorkDays(previousIntervalDate, endPhra.getEffectiveDate());
234                                                            boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(),  anAC.getAccrualEarnInterval(), 
235                                                                                    workDaysInBetween, new java.sql.Date(lastIntervalDate.getTime()),
236                                                                                    phra.getPayCalendar(), rrAggregate.getCalEntryMap());
237                                                            if(prorationFlag) {
238                                                                    if(minReachedFlag) {
239                                                                            // minimum reached, proration = true, first pay period, then use actual work days of currentRule for calculation
240                                                                            // so nothing special needs to be done here
241                                                                    } else {
242                                                                            // min NOT reached, proration = true, first pay period, then no accrual for this pay period
243                                                                            accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
244                                                                            accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
245                                                                            continue;
246                                                                    }
247                                                            } else {
248                                                                    if(minReachedFlag) {
249                                                                            //  minimum reached, proration = false, first pay period, then accrual the whole fte of current AC rule for this pay interval
250                                                                            accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), currentAcRule.getAccrualRate());
251                                                                            fullFteGranted = true;
252                                                                    } else {
253                                                                            // min NOT reached, proration = false, first pay period, then no accrual for this pay period
254                                                                            accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
255                                                                            accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
256                                                                            continue;
257                                                                    }
258                                                            }
259                                                    }
260                                            }
261                                                                                    
262                                            if(currentAcRule == null) {
263                                                    accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
264                                                    accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
265                                                    continue;
266                                            }
267                                            
268                                            // only accrual on work days
269                                            if(!TKUtils.isWeekend(currentDate) && !fullFteGranted) {
270                                                    BigDecimal accrualRate = currentAcRule.getAccrualRate();
271                                                    int numberOfWorkDays = this.getWorkDaysInInterval(new java.sql.Date(currentDate.getTime()), anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
272                                                    BigDecimal dayRate = numberOfWorkDays > 0 ? accrualRate.divide(new BigDecimal(numberOfWorkDays), 6, BigDecimal.ROUND_HALF_UP) : new BigDecimal(0);
273                                                    //Fetch the accural rate based on rate range for today(Rate range is the accumulated list of jobs and accrual rate for today)
274                                                    //Add to total accumulatedAccrualCatToAccrualAmounts
275                                                    //use rule and ftePercentage to calculate the hours                                             
276                                                    this.calculateHours(anAC.getLmAccrualCategoryId(), ftePercentage, dayRate, accumulatedAccrualCatToAccrualAmounts);
277                                                    
278                                                    //get not eligible for accrual hours based on leave block on this day
279                                                    BigDecimal noAccrualHours = getNotEligibleForAccrualHours(principalId, new java.sql.Date(currentDate.getTime()));
280                                                    
281                                                    if(noAccrualHours.compareTo(BigDecimal.ZERO) != 0 && totalOfStandardHours.compareTo(BigDecimal.ZERO) != 0) {
282                                                            BigDecimal dayHours = totalOfStandardHours.divide(new BigDecimal(5), 6, BigDecimal.ROUND_HALF_UP);
283                                                            BigDecimal noAccrualRate = dayRate.multiply(noAccrualHours.divide(dayHours));
284                                                            this.calculateHours(anAC.getLmAccrualCategoryId(), ftePercentage, noAccrualRate, accumulatedAccrualCatToNegativeAccrualAmounts);
285                                                    }
286                                            }                                       
287                                            //Determine if we are at the accrual earn interval in the span, if so add leave block for accumulated accrual amount to list
288                                            //and reset accumulatedAccrualCatToAccrualAmounts and accumulatedAccrualCatToNegativeAccrualAmounts for this accrual category
289                                            if(this.isDateAnIntervalDate(currentDate, anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap())) {
290                                                    BigDecimal acHours = accumulatedAccrualCatToAccrualAmounts.get(anAC.getLmAccrualCategoryId());
291                                                    
292                                                    if(acHours != null) {
293                                                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate, acHours, anAC, null, true, currentRange.getLeaveCalendarDocumentId());
294                                                            accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());    // reset accumulatedAccrualCatToAccrualAmounts
295                                                            fullFteGranted = false;
296                                                    }
297                                                    
298                                                    BigDecimal adjustmentHours = accumulatedAccrualCatToNegativeAccrualAmounts.get(anAC.getLmAccrualCategoryId());
299                                                    if(adjustmentHours != null && adjustmentHours.compareTo(BigDecimal.ZERO) != 0) {
300                                                            // do not create leave block if the ajustment amount is 0
301                                                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate, adjustmentHours, anAC, null, false, currentRange.getLeaveCalendarDocumentId());
302                                                            accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());    // reset accumulatedAccrualCatToNegativeAccrualAmounts
303                                                    }
304                                            }                       
305                                    }
306                            }
307                            //Determine if today is a system scheduled time off and accrue holiday if so.
308                            SystemScheduledTimeOff ssto = currentRange.getSysScheTimeOff();
309                            if(ssto != null) {
310                                    AccrualCategory anAC = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(ssto.getAccrualCategory(), ssto.getEffectiveDate());
311                                    if(anAC == null) {
312                                            throw new RuntimeException("Cannot find Accrual Category for system scheduled time off " + ssto.getLmSystemScheduledTimeOffId());
313                                    }
314                                    BigDecimal hrs = ssto.getAmountofTime().multiply(ftePercentage);
315                                    // system scheduled time off leave block
316                                    createLeaveBlock(principalId, accrualLeaveBlocks, currentDate, hrs, anAC, ssto.getLmSystemScheduledTimeOffId(), true, currentRange.getLeaveCalendarDocumentId());
317                                    // usage leave block with negative amount
318                                    createLeaveBlock(principalId, accrualLeaveBlocks, currentDate, hrs.negate(), anAC, ssto.getLmSystemScheduledTimeOffId(), true, currentRange.getLeaveCalendarDocumentId());
319                            }
320                            // if today is the last day of the employment, create leave blocks if there's any hours available
321                            if(endPhra != null && TKUtils.removeTime(currentDate).equals(TKUtils.removeTime(endPhra.getEffectiveDate()))){
322                                    // accumulated accrual amount
323                                    if(!accumulatedAccrualCatToAccrualAmounts.isEmpty()) {
324                                            for(Map.Entry<String, BigDecimal> entry : accumulatedAccrualCatToAccrualAmounts.entrySet()) {
325                                                    if(entry.getValue() != null && entry.getValue().compareTo(BigDecimal.ZERO) != 0) {
326                                                            AccrualCategory anAC = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(entry.getKey());
327                                                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate, entry.getValue(), anAC, null, true, currentRange.getLeaveCalendarDocumentId());
328                                                    }
329                                            }
330                                            accumulatedAccrualCatToAccrualAmounts = new HashMap<String,BigDecimal>(); // reset accumulatedAccrualCatToAccrualAmounts
331                                    }
332                                    // negative/adjustment accrual amount
333                                    if(!accumulatedAccrualCatToNegativeAccrualAmounts.isEmpty()) {
334                                            for(Map.Entry<String, BigDecimal> entry : accumulatedAccrualCatToNegativeAccrualAmounts.entrySet()) {
335                                                    if(entry.getValue() != null && entry.getValue().compareTo(BigDecimal.ZERO) != 0) {
336                                                            AccrualCategory anAC = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(entry.getKey());
337                                                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate, entry.getValue(), anAC, null, true, currentRange.getLeaveCalendarDocumentId());
338                                                    }
339                                            }
340                                            accumulatedAccrualCatToNegativeAccrualAmounts = new HashMap<String,BigDecimal>(); // reset accumulatedAccrualCatToNegativeAccrualAmounts
341                                    }
342                                    phra = null;    // reset principal attribute so new value will be retrieved
343                                    endPhra = null; // reset end principal attribute so new value will be retrieved
344                            }
345                            
346                            aCal.add(Calendar.DATE, 1);
347                    }
348                    
349                    //Save accrual leave blocks at the very end
350                    TkServiceLocator.getLeaveBlockService().saveLeaveBlocks(accrualLeaveBlocks);
351                    
352                    // record timestamp of this accrual run in database
353                    if(recordRanData) {
354                            TkServiceLocator.getPrincipalAccrualRanService().updatePrincipalAccrualRanInfo(principalId);
355                    }
356                    
357            }
358            
359            private void deactivateOldAccruals(String principalId, Date startDate, Date endDate, String runAsPrincipalId) {
360                    List<LeaveBlock> previousLB = TkServiceLocator.getLeaveBlockService().getAccrualGeneratedLeaveBlocks(principalId, startDate, endDate);
361                    List<LeaveBlock> sstoAccrualList = new ArrayList<LeaveBlock>();
362                    List<LeaveBlock> sstoUsageList = new ArrayList<LeaveBlock>();
363                    
364                    for(LeaveBlock lb : previousLB) {
365                            if(StringUtils.isNotEmpty(lb.getScheduleTimeOffId())) {
366                                    if(lb.getLeaveAmount().compareTo(BigDecimal.ZERO) > 0) {
367                                            sstoAccrualList.add(lb);
368                                    } else if(lb.getLeaveAmount().compareTo(BigDecimal.ZERO) < 0) {
369                                            sstoUsageList.add(lb);
370                                    }
371                            } else {
372                                    TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(), runAsPrincipalId);
373                            }
374                    }
375                    
376                    for(LeaveBlock accrualLb : sstoAccrualList) {
377                            for(LeaveBlock usageLb : sstoUsageList) {
378                                    // both usage and accrual ssto leave blocks are there, so the ssto accural is not banked, removed both leave blocks
379                                    // if this is no ssto usage leave block, it means the user has banked this ssto hours. Don't delete this ssto accrual leave block
380                                    if(accrualLb.getScheduleTimeOffId().equals(usageLb.getScheduleTimeOffId())) {   
381                                            TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(accrualLb.getLmLeaveBlockId(), runAsPrincipalId);
382                                            TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(usageLb.getLmLeaveBlockId(), runAsPrincipalId);
383                                    }
384                            }
385                    }
386                    
387            }
388            
389            private BigDecimal getNotEligibleForAccrualHours(String principalId, Date currentDate) {
390                    BigDecimal hours = BigDecimal.ZERO;
391                    // check if there's any manual not-eligible-for-accrual leave blocks, use the hours of the leave block to adjust accrual calculation 
392                    List<LeaveBlock> lbs = TkServiceLocator.getLeaveBlockService().getNotAccrualGeneratedLeaveBlocksForDate(principalId, currentDate);
393                    for(LeaveBlock lb : lbs) {
394                            EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), currentDate);
395                            if(ec == null) {
396                                    throw new RuntimeException("Cannot find Earn Code for Leave block " + lb.getLmLeaveBlockId());
397                            }
398                            if(ec.getEligibleForAccrual().equals("N") && lb.getLeaveAmount().compareTo(BigDecimal.ZERO) != 0) {
399                                    hours = hours.add(lb.getLeaveAmount());
400                            }               
401                    }
402                    return hours;
403            }
404            
405            private void createLeaveBlock(String principalId, List<LeaveBlock> accrualLeaveBlocks, 
406                            java.util.Date currentDate, BigDecimal hrs, AccrualCategory anAC, String sysSchTimeOffId, 
407                            boolean createZeroLeaveBlock, String leaveDocId) {
408                    // Replacing Leave Code to earn code - KPME 1634
409                    EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(anAC.getEarnCode(), anAC.getEffectiveDate());
410                    if(ec == null) {
411                            throw new RuntimeException("Cannot find Earn Code for Accrual category " + anAC.getAccrualCategory());
412                    }
413                    // use rounding option and fract time allowed of Leave Code to round the leave block hours
414                    BigDecimal roundedHours = TkServiceLocator.getEarnCodeService().roundHrsWithEarnCode(hrs, ec);
415                    if(!createZeroLeaveBlock && roundedHours.compareTo(BigDecimal.ZERO) == 0) {
416                            return; // do not create leave block with zero amount
417                    }
418                    LeaveBlock aLeaveBlock = new LeaveBlock();
419                    aLeaveBlock.setAccrualCategory(anAC.getAccrualCategory());
420                    aLeaveBlock.setLeaveDate(new java.sql.Date(currentDate.getTime()));
421                    aLeaveBlock.setPrincipalId(principalId);
422                    //More than one earn code can be associated with an accrual category. Which one does this get?
423                    aLeaveBlock.setEarnCode(anAC.getEarnCode());
424                    aLeaveBlock.setDateAndTime(new Timestamp(currentDate.getTime()));
425                    aLeaveBlock.setAccrualGenerated(true);
426                    aLeaveBlock.setBlockId(0L);
427                    aLeaveBlock.setScheduleTimeOffId(sysSchTimeOffId);
428                    aLeaveBlock.setLeaveAmount(roundedHours);
429                    aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
430                    aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
431                    aLeaveBlock.setDocumentId(leaveDocId);
432                    
433                    accrualLeaveBlocks.add(aLeaveBlock);
434                    
435            }
436            
437            private void createEmptyLeaveBlockForStatusChange(String principalId, List<LeaveBlock> accrualLeaveBlocks, java.util.Date currentDate) {
438                    LeaveBlock aLeaveBlock = new LeaveBlock();
439                    aLeaveBlock.setAccrualCategory(null);
440                    aLeaveBlock.setLeaveDate(new java.sql.Date(currentDate.getTime()));
441                    aLeaveBlock.setPrincipalId(principalId);
442                    aLeaveBlock.setEarnCode(LMConstants.STATUS_CHANGE_EARN_CODE);   // fake leave code
443                    aLeaveBlock.setDateAndTime(new Timestamp(currentDate.getTime()));
444                    aLeaveBlock.setAccrualGenerated(true);
445                    aLeaveBlock.setBlockId(0L);
446                    aLeaveBlock.setScheduleTimeOffId(null);
447                    aLeaveBlock.setLeaveAmount(BigDecimal.ZERO);
448                    aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
449                    aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
450                    
451                    accrualLeaveBlocks.add(aLeaveBlock);
452                    
453            }
454    
455            private void calculateHours(String accrualCategoryId, BigDecimal fte, BigDecimal rate, Map<String, BigDecimal> accumulatedAmounts ) {
456                    BigDecimal hours = rate.multiply(fte);
457                    BigDecimal oldHours = accumulatedAmounts.get(accrualCategoryId);
458                    BigDecimal newHours = oldHours == null ? hours : hours.add(oldHours);
459                    accumulatedAmounts.put(accrualCategoryId, newHours);
460            }
461            
462            public Date getStartAccrualDate(String principalId){
463                    return null;
464            }
465            
466            public Date getEndAccrualDate(String principalId){
467                    //KPME-1246  Fetch planning months
468                    
469                    return null;
470            }
471    
472            @Override
473            public void runAccrual(List<String> principalIds) {
474                    for(String principalId : principalIds){
475                            runAccrual(principalId);
476                    }
477            }
478            
479            private boolean isDateAnIntervalDate(java.util.Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
480                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
481                            return isDateAtPayCalInterval(aDate, earnInterval, payCalName, aMap);
482                    } else {
483                            return this.isDateAtEarnInterval(aDate, earnInterval);
484                    }
485            }
486            
487            private boolean isDateAtPayCalInterval(java.util.Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
488                    if(StringUtils.isNotEmpty(payCalName) 
489                                    && !aMap.isEmpty()
490                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
491                            List<CalendarEntries> entryList = aMap.get(payCalName);
492                            if(CollectionUtils.isNotEmpty(entryList)) {
493                                    for(CalendarEntries anEntry : entryList) {
494                                            // endPeriodDate of calendar entry is the beginning hour of the next day, so we need to substract one day from it to get the real end date
495                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
496                                            if(aDate.compareTo(endDate) == 0) {
497                                                    return true;
498                                            }
499                                    }
500                            }
501                    }
502                    return false;
503            }
504            
505            @Override
506            public boolean isDateAtEarnInterval(java.util.Date aDate, String earnInterval) {
507                    boolean atEarnInterval = false;
508                    if(LMConstants.ACCRUAL_EARN_INTERVAL_MAP.containsKey(earnInterval)) {
509                            Calendar aCal = Calendar.getInstance();
510                            aCal.setTime(aDate);
511                            
512                            if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
513                                    atEarnInterval = true;
514                            } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
515                                    // figure out if the day is a Saturday
516                                    if(aCal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
517                                            atEarnInterval = true;
518                                    }
519                            } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
520                                    // either the 15th or the last day of the month
521                                    if(aCal.get(Calendar.DAY_OF_MONTH) == 15 || aCal.get(Calendar.DAY_OF_MONTH) == aCal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
522                                            atEarnInterval = true;
523                                    }
524                            } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
525                                    // the last day of the month
526                                    if(aCal.get(Calendar.DAY_OF_MONTH) == aCal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
527                                            atEarnInterval = true;
528                                    }
529                            } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
530                                    // the last day of the year
531                                    if(aCal.get(Calendar.DAY_OF_YEAR) == aCal.getActualMaximum(Calendar.DAY_OF_YEAR)) {
532                                            atEarnInterval = true;
533                                    }
534                            }else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
535                                    // no calculation
536                            }
537                    }
538                    return atEarnInterval;
539            }
540    
541    
542            @Override
543            public RateRangeAggregate buildRateRangeAggregate(String principalId, Date startDate, Date endDate) {
544                    RateRangeAggregate rrAggregate = new RateRangeAggregate();
545                    List<RateRange> rateRangeList = new ArrayList<RateRange>(); 
546                    Calendar gc = new GregorianCalendar();
547                    gc.setTime(startDate);
548                    // get all active jobs that are effective before the endDate
549                    List<Job> activeJobs = TkServiceLocator.getJobService().getAllActiveLeaveJobs(principalId, endDate);
550                    List<Job> inactiveJobs = TkServiceLocator.getJobService().getAllInActiveLeaveJobsInRange(principalId, startDate, endDate);
551                    
552                    List<PrincipalHRAttributes> phaList = TkServiceLocator.getPrincipalHRAttributeService().getAllActivePrincipalHrAttributesForPrincipalId(principalId, endDate);
553                    List<PrincipalHRAttributes> inactivePhaList = TkServiceLocator.getPrincipalHRAttributeService().getAllInActivePrincipalHrAttributesForPrincipalId(principalId, endDate);
554                    
555                    if(activeJobs.isEmpty() || phaList.isEmpty()) {
556                            return rrAggregate;
557                    }
558                    
559                    Set<String> phaLpSet = new HashSet<String>();
560                    Set<String> calNameSet = new HashSet<String>();
561                    if(CollectionUtils.isNotEmpty(phaList)) {
562                            for(PrincipalHRAttributes pha : phaList) {
563                                    phaLpSet.add(pha.getLeavePlan());
564                                    calNameSet.add(pha.getPayCalendar());
565                            }
566                    }
567                    
568                    List<LeavePlan> activeLpList = new ArrayList<LeavePlan> ();
569                    List<LeavePlan> inactiveLpList = new ArrayList<LeavePlan> ();
570                    for(String lpString : phaLpSet) {
571                            List<LeavePlan> aList = TkServiceLocator.getLeavePlanService().getAllActiveLeavePlan(lpString, endDate);
572                            activeLpList.addAll(aList);
573                            
574                            aList = TkServiceLocator.getLeavePlanService().getAllInActiveLeavePlan(lpString, endDate);
575                            inactiveLpList.addAll(aList);
576                    }
577                    
578                    // get all pay calendar entries for this employee. used to determine interval dates
579                    Map<String, List<CalendarEntries>> calEntryMap = new HashMap<String, List<CalendarEntries>>();
580                    for(String calName : calNameSet) {
581                            org.kuali.hr.time.calendar.Calendar aCal = TkServiceLocator.getCalendarService().getCalendarByGroup(calName);
582                            if(aCal != null) {
583                                    List<CalendarEntries> aList = TkServiceLocator.getCalendarEntriesService().getAllCalendarEntriesForCalendarId(aCal.getHrCalendarId());
584                                    Collections.sort(aList);
585                                    calEntryMap.put(calName, aList);
586                            }
587                    }
588                    rrAggregate.setCalEntryMap(calEntryMap);                
589                    
590                    Set<String> lpStringSet = new HashSet<String>();
591                    if(CollectionUtils.isNotEmpty(activeLpList)) {
592                            for(LeavePlan lp : activeLpList) {
593                                    lpStringSet.add(lp.getLeavePlan());
594                            }
595                    }
596                    List<SystemScheduledTimeOff> sstoList = new ArrayList<SystemScheduledTimeOff>();
597                    for(String lpString : lpStringSet) {
598                            List<SystemScheduledTimeOff> aList =TkServiceLocator.getSysSchTimeOffService().getSystemScheduledTimeOffsForLeavePlan(startDate, endDate, lpString);
599                            if(CollectionUtils.isNotEmpty(aList)) {
600                                    sstoList.addAll(aList);
601                            }
602                    }
603                    
604                    List<AccrualCategory> activeAccrCatList = new ArrayList<AccrualCategory>();
605                    List<AccrualCategory> inactiveAccrCatList = new ArrayList<AccrualCategory>();
606                    for(String lpString : lpStringSet) {
607                            List<AccrualCategory> aList = TkServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate);
608                            if(CollectionUtils.isNotEmpty(aList)) {
609                                    activeAccrCatList.addAll(aList);
610                            }
611                            
612                            aList = TkServiceLocator.getAccrualCategoryService().getInActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate);
613                            if(CollectionUtils.isNotEmpty(aList)) {
614                                    inactiveAccrCatList.addAll(aList);
615                            }
616                    }
617                    
618                    List<AccrualCategoryRule> activeRuleList = new ArrayList<AccrualCategoryRule>();
619                    List<AccrualCategoryRule> inactiveRuleList = new ArrayList<AccrualCategoryRule>();
620                    for(AccrualCategory ac : activeAccrCatList) {
621                            List<AccrualCategoryRule> aRuleList = TkServiceLocator.getAccrualCategoryRuleService().getActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId(), endDate);
622                            activeRuleList.addAll(aRuleList);
623                            
624                            aRuleList = TkServiceLocator.getAccrualCategoryRuleService().getInActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId(), endDate);
625                            inactiveRuleList.addAll(aRuleList);
626                    }
627                    
628                    List<LeaveCalendarDocumentHeader> lcDocList = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getAllDocumentHeadersInRangeForPricipalId(principalId, startDate, endDate);
629                    
630                    BigDecimal previousFte = null;
631                    List<Job> jobs = new ArrayList<Job>();
632                    
633                while (!gc.getTime().after(endDate)) {
634                    RateRange rateRange = new RateRange();
635                    java.util.Date currentDate = gc.getTime();
636                    
637                    jobs = this.getJobsForDate(activeJobs, inactiveJobs, currentDate);
638                    if(jobs.isEmpty()) {    // no jobs found for this day
639                            gc.add(Calendar.DATE, 1);
640                            continue;
641                    }
642                            rateRange.setJobs(jobs);
643                            
644                            // detect if there's a status change
645                            BigDecimal fteSum = TkServiceLocator.getJobService().getFteSumForJobs(jobs);
646                            rateRange.setAccrualRatePercentageModifier(fteSum);
647                            BigDecimal standardHours = TkServiceLocator.getJobService().getStandardHoursSumForJobs(jobs);
648                            rateRange.setStandardHours(standardHours);
649                            
650                            if(previousFte != null && !previousFte.equals(fteSum)) {
651                                    rateRange.setStatusChanged(true);
652                                    rrAggregate.setRateRangeChanged(true);
653                            }
654                            previousFte = fteSum;
655                            
656                            // figure out the PrincipalHRAttributes for this day
657                            PrincipalHRAttributes phra = this.getPrincipalHrAttributesForDate(phaList, currentDate);
658                            rateRange.setPrincipalHRAttributes(phra);
659                            
660                            if(rateRange.getPrincipalHRAttributes() != null) {
661                                    // figure out if there's an end principalHrAttributes for the initial principalHRAttributes
662                                    PrincipalHRAttributes endPhra = this.getInactivePrincipalHrAttributesForDate(inactivePhaList, rateRange.getPrincipalHRAttributes().getEffectiveDate(), currentDate);
663                                    rateRange.setEndPrincipalHRAttributes(endPhra);
664                            }
665                            
666                            // get leave plan for this day
667                            if(rateRange.getPrincipalHRAttributes()!= null) {                               
668                                    rateRange.setLeavePlan(this.getLeavePlanForDate(activeLpList, inactiveLpList, rateRange.getPrincipalHRAttributes().getLeavePlan(), currentDate));
669                            }
670                            
671                            if(rateRange.getLeavePlan() != null) {
672                                    // get accrual category list for this day
673                                    List<AccrualCategory> acsForDay = this.getAccrualCategoriesForDate(activeAccrCatList, inactiveAccrCatList, rateRange.getLeavePlan().getLeavePlan(), currentDate);
674                                    rateRange.setAcList(acsForDay);
675                                    
676                                    // get System scheduled time off for this day
677                                    for(SystemScheduledTimeOff ssto : sstoList) {
678                                            if(TKUtils.removeTime(ssto.getAccruedDate()).equals(TKUtils.removeTime(currentDate) )
679                                                            && ssto.getLeavePlan().equals(rateRange.getLeavePlan().getLeavePlan())) {
680                                                    // if there exists a ssto accrualed leave block with this ssto id, it means the ssto hours has been banked or transferred by the employee
681                                                    // this logic depends on the deactivateOldAccruals() runs before buildRateRangeAggregate()
682                                                    // because deactivateOldAccruals() removes accrued ssto leave blocks unless they are banked/transferred
683                                                    List<LeaveBlock> sstoLbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(principalId, ssto.getLmSystemScheduledTimeOffId(), ssto.getAccruedDate());
684                                                    if(CollectionUtils.isEmpty(sstoLbList)) {
685                                                            rateRange.setSysScheTimeOff(ssto);
686                                                    }
687                                            }       
688                                    }
689                            }
690                            // set accrual category rules for the day
691                            if(CollectionUtils.isNotEmpty(rateRange.getAcList())) {
692                                    List<AccrualCategoryRule> rulesForDay = new ArrayList<AccrualCategoryRule>();
693                                    for(AccrualCategory ac : rateRange.getAcList()) {
694                                            rulesForDay.addAll(this.getAccrualCategoryRulesForDate
695                                                                                            (activeRuleList, ac.getLmAccrualCategoryId(), currentDate, rateRange.getPrincipalHRAttributes().getServiceDate()));
696                                    }
697                                    rateRange.setAcRuleList(rulesForDay);
698                            
699                            }
700                            
701                            DateTime beginInterval = new DateTime(gc.getTime());
702                            gc.add(Calendar.DATE, 1);
703                            DateTime endInterval = new DateTime(gc.getTime());
704                            Interval range = new Interval(beginInterval, endInterval);
705                            rateRange.setRange(range);
706                            // assign leave document id to range if there is an existing leave doc for currentDate.
707                            // The doc Id will be assigned to leave blocks created at this rate range
708                            rateRange.setLeaveCalendarDocumentId(this.getLeaveDocumentForDate(lcDocList, currentDate));
709                            rateRangeList.add(rateRange);          
710                }
711                    rrAggregate.setRateRanges(rateRangeList);
712                    rrAggregate.setCurrentRate(null);
713                    return rrAggregate;
714            }
715            
716            private String getLeaveDocumentForDate(List<LeaveCalendarDocumentHeader> lcDocList, java.util.Date currentDate) {
717                    for(LeaveCalendarDocumentHeader lcdh : lcDocList) {
718                            if(!lcdh.getBeginDate().after(currentDate) && lcdh.getEndDate().after(currentDate)) {
719                                    return lcdh.getDocumentId();
720                            }
721                    }
722                    return "";
723            }
724                    
725            public List<Job> getJobsForDate(List<Job> activeJobs, List<Job> inactiveJobs, java.util.Date currentDate) {
726                    List<Job> jobs = new ArrayList<Job>();
727            for(Job aJob : activeJobs) {
728                    if(!aJob.getEffectiveDate().after(currentDate)) {
729                            jobs.add(aJob);
730                    }
731            }
732            if(CollectionUtils.isNotEmpty(jobs)) {
733                    List<Job> tempList = new ArrayList<Job>();
734                    tempList.addAll(jobs);
735                    for(Job aJob : tempList) {
736                            for(Job inactiveJob : inactiveJobs) {
737                                    if(inactiveJob.getJobNumber().equals(aJob.getJobNumber())
738                                            && inactiveJob.getEffectiveDate().after(aJob.getEffectiveDate())
739                                            && !inactiveJob.getEffectiveDate().after(currentDate)) {
740                                                    // remove inactive job from list
741                                                    jobs.remove(aJob);
742                                    }
743                            }
744                    }
745            }
746            return jobs;
747            }
748            
749            public PrincipalHRAttributes getPrincipalHrAttributesForDate(List<PrincipalHRAttributes> activeList, java.util.Date currentDate) {
750                    List<PrincipalHRAttributes> phasForDay = new ArrayList<PrincipalHRAttributes>();
751                    for(PrincipalHRAttributes pha : activeList) {
752                            if(!pha.getEffectiveDate().after(currentDate) && !pha.getServiceDate().after(currentDate)) {
753                            phasForDay.add(pha);
754                    }
755                    }
756                    if(CollectionUtils.isNotEmpty(phasForDay)) {
757                            PrincipalHRAttributes pha = phasForDay.get(0);
758                            int indexOfMaxEffDt = 0;
759                            if(phasForDay.size() > 1) {
760                                    for(int i = 1; i < phasForDay.size(); i++) {
761                                            if( (phasForDay.get(i).getEffectiveDate().after(phasForDay.get(indexOfMaxEffDt).getEffectiveDate()))
762                                                            ||(phasForDay.get(i).getEffectiveDate().equals(phasForDay.get(indexOfMaxEffDt).getEffectiveDate())
763                                                                            && phasForDay.get(i).getTimestamp().after(phasForDay.get(indexOfMaxEffDt).getTimestamp()))) {
764                                                    indexOfMaxEffDt = i;
765                                            }
766                                    }
767                                    pha = phasForDay.get(indexOfMaxEffDt);
768                            }
769                            return pha;
770                    }
771                    return null;
772            }
773            
774            public PrincipalHRAttributes getInactivePrincipalHrAttributesForDate(List<PrincipalHRAttributes> inactiveList, java.util.Date activeDate, java.util.Date currentDate) {
775                    List<PrincipalHRAttributes> inactivePhasForDay = new ArrayList<PrincipalHRAttributes>();
776                    for(PrincipalHRAttributes pha : inactiveList) {
777                            if( pha.getEffectiveDate().after(activeDate) && !pha.getServiceDate().after(currentDate)) {
778                                    inactivePhasForDay.add(pha);
779                    }
780                    }
781                    if(CollectionUtils.isNotEmpty(inactivePhasForDay)) {
782                            PrincipalHRAttributes pha = inactivePhasForDay.get(0);
783                            int indexOfMaxEffDt = 0;
784                            if(inactivePhasForDay.size() > 1) {
785                                    for(int i = 1; i < inactivePhasForDay.size(); i++) {
786                                            if( (inactivePhasForDay.get(i).getEffectiveDate().after(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveDate()))
787                                                            ||(inactivePhasForDay.get(i).getEffectiveDate().equals(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveDate())
788                                                                            && inactivePhasForDay.get(i).getTimestamp().after(inactivePhasForDay.get(indexOfMaxEffDt).getTimestamp()))) {
789                                                    indexOfMaxEffDt = i;
790                                            }
791                                    }
792                                    pha = inactivePhasForDay.get(indexOfMaxEffDt);
793                            }
794                            return pha;
795                    }
796                    return null;
797            }
798            
799            public LeavePlan getLeavePlanForDate(List<LeavePlan> activeLpList, List<LeavePlan> inactiveLpList, String leavePlan, java.util.Date currentDate) {
800                    List<LeavePlan> lpsForDay = new ArrayList<LeavePlan>();
801                    for(LeavePlan lp : activeLpList) {
802                            if(lp.getLeavePlan().equals(leavePlan) && !lp.getEffectiveDate().after(currentDate)) {
803                                    lpsForDay.add(lp);
804                            }
805                    }
806                    List<LeavePlan> aList = new ArrayList<LeavePlan>();
807                    aList.addAll(lpsForDay);
808            for(LeavePlan lp : aList) {
809                    for(LeavePlan inactiveLp : inactiveLpList) {
810                            if(inactiveLp.getLeavePlan().equals(lp.getLeavePlan())
811                                    && inactiveLp.getEffectiveDate().after(lp.getEffectiveDate())
812                                    && !inactiveLp.getEffectiveDate().after(currentDate)) {
813                                            // remove inactive leave plan from list
814                                            lpsForDay.remove(lp);
815                            }
816                    }
817            }
818                    if(CollectionUtils.isNotEmpty(lpsForDay)) {
819                            LeavePlan aLp = lpsForDay.get(0);
820                            int indexOfMaxEffDt = 0;
821                            if(lpsForDay.size() > 1) {
822                                    for(int i = 1; i < lpsForDay.size(); i++) {
823                                            if( (lpsForDay.get(i).getEffectiveDate().after(lpsForDay.get(indexOfMaxEffDt).getEffectiveDate()))
824                                                            ||(lpsForDay.get(i).getEffectiveDate().equals(lpsForDay.get(indexOfMaxEffDt).getEffectiveDate())
825                                                                            && lpsForDay.get(i).getTimestamp().after(lpsForDay.get(indexOfMaxEffDt).getTimestamp()))) {
826                                                    indexOfMaxEffDt = i;
827                                            }
828                                    }
829                                    aLp = lpsForDay.get(indexOfMaxEffDt);
830                            }
831                            return aLp;
832                    }
833                    return null;
834            }
835            
836            public List<AccrualCategory> getAccrualCategoriesForDate(List<AccrualCategory> activeAccrCatList, List<AccrualCategory> inactiveAccrCatList, String leavePlan, java.util.Date currentDate) {
837                    Set<AccrualCategory> aSet = new HashSet<AccrualCategory>();
838                    for(AccrualCategory ac : activeAccrCatList) {
839                            if(ac.getLeavePlan().equals(leavePlan) && !ac.getEffectiveDate().after(currentDate)) {
840                                    aSet.add(ac);
841                            }
842                    }
843                    List<AccrualCategory> list1 = new ArrayList<AccrualCategory>();
844                    list1.addAll(aSet);
845            for(AccrualCategory ac : list1) {
846                    for(AccrualCategory inactiveAc : inactiveAccrCatList) {
847                            if(inactiveAc.getAccrualCategory().equals(ac.getAccrualCategory())
848                                    && inactiveAc.getEffectiveDate().after(ac.getEffectiveDate())
849                                    && !inactiveAc.getEffectiveDate().after(currentDate)) {
850                                            // remove inactive accrual category from list
851                                    aSet.remove(ac);
852                            }
853                    }
854            }
855            List<AccrualCategory> acsForDay = new ArrayList<AccrualCategory>();
856            acsForDay.addAll(aSet);
857            return acsForDay;       
858            }
859            
860            @Override
861            public boolean isEmpoyeementFutureStatusChanged(String principalId, Date startDate, Date endDate) {
862                    Date currentDate = TKUtils.getCurrentDate();
863                    if(endDate.after(currentDate)) {
864                            RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate);
865                            if(rrAggregate.isRateRangeChanged()) {
866                                    return true;
867                            }
868                    }
869                    return false;
870            }
871            
872            @Override
873            public void calculateFutureAccrualUsingPlanningMonth(String principalId, Date asOfDate) {
874                    PrincipalHRAttributes phra = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, asOfDate);
875                    if(phra != null) {
876                            // use the date from pay period to get the leave plan
877                            LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(phra.getLeavePlan(), asOfDate);  
878                            if(lp != null && StringUtils.isNotEmpty(lp.getPlanningMonths())) {
879                                    Calendar aCal = Calendar.getInstance();
880                                    // go back a year 
881                                    aCal.setTime(asOfDate);
882                                    aCal.add(Calendar.YEAR, -1);
883                                    if(aCal.getActualMaximum(Calendar.DAY_OF_MONTH) < aCal.get(Calendar.DATE)) {
884                                            aCal.set(Calendar.DATE, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
885                                    }
886                                    Date startDate = new java.sql.Date(aCal.getTime().getTime());
887                                    // go forward using planning months
888                                    aCal.setTime(asOfDate);
889                                    aCal.add(Calendar.MONTH, Integer.parseInt(lp.getPlanningMonths()));
890                                    // max days in months differ, if the date is bigger than the max day, set it to the max day of the month
891                                    if(aCal.getActualMaximum(Calendar.DAY_OF_MONTH) < aCal.get(Calendar.DATE)) {
892                                            aCal.set(Calendar.DATE, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
893                                    }
894                                    Date endDate = new java.sql.Date(aCal.getTime().getTime());
895                                    TkServiceLocator.getLeaveAccrualService().runAccrual(principalId, startDate, endDate, true);
896                            }
897                    }
898            }
899            
900            private boolean minimumPercentageReachedForPayPeriod(BigDecimal min, String earnInterval, int workDays, Date intervalDate, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
901                    if(min == null || min.compareTo(BigDecimal.ZERO) == 0) {
902                            return true;
903                    }
904                    int daysInInterval = this.getWorkDaysInInterval(intervalDate, earnInterval, payCalName, aMap);
905                    if(daysInInterval == 0) {
906                            return true;
907                    }
908                    BigDecimal actualPercentage =  new BigDecimal(workDays).divide(new BigDecimal(daysInInterval), 2, BigDecimal.ROUND_HALF_EVEN);
909                    if(actualPercentage.compareTo(min) >= 0) {
910                            return true;
911                    }
912                    
913                    return false;   
914            }
915    
916            private java.util.Date getPrevIntervalDate(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
917                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
918                            return this.getPrevPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
919                    } else {
920                            return this.getPreviousAccrualIntervalDate(earnInterval, aDate);
921                    }
922            }
923            
924            @Override
925            public java.util.Date getPreviousAccrualIntervalDate(String earnInterval, Date aDate) {
926                    Calendar aCal = Calendar.getInstance();
927                    aCal.setTime(aDate);
928    
929                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
930                            aCal.add(Calendar.DAY_OF_YEAR, -1);
931                    } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
932                            aCal.add(Calendar.WEEK_OF_YEAR, -1);
933                            aCal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);      // set to the Saturday of previous week
934                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
935                            aCal.add(Calendar.DAY_OF_YEAR, -15);
936                            if(aCal.get(Calendar.DAY_OF_MONTH) <=15) {
937                                    aCal.set(Calendar.DAY_OF_MONTH, 15);
938                            } else {
939                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
940                            }
941                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
942                            aCal.add(Calendar.MONTH, -1);
943                            aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
944                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
945                            aCal.add(Calendar.YEAR, -1);
946                            aCal.set(Calendar.DAY_OF_YEAR, aCal.getActualMaximum(Calendar.DAY_OF_YEAR));
947                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
948                            // no change to calendar
949                    } 
950                    return aCal.getTime();
951            }
952            
953            private java.util.Date getPrevPayCalIntervalDate(java.util.Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
954                    if(StringUtils.isNotEmpty(payCalName) 
955                                    && !aMap.isEmpty()
956                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
957                            List<CalendarEntries> entryList = aMap.get(payCalName);
958                            if(CollectionUtils.isNotEmpty(entryList)) {
959                                    for(CalendarEntries anEntry : entryList) {
960                                            // endPeriodDate of calendar entry is the beginning hour of the next day, so we need to substract one day from it to get the real end date
961                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
962                                            if(anEntry.getBeginPeriodDate().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
963                                                    // the day before the beginning date of the cal entry that contains the passed in date is the endDate of previous calendar entry
964                                                    java.util.Date prevIntvDate = TKUtils.addDates(anEntry.getBeginPeriodDate(), -1);
965                                                    return prevIntvDate;
966                                            }
967                                    }
968                            }
969                    }
970                    return aDate;
971            }
972            
973            private java.util.Date getNextIntervalDate(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
974                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
975                            return this.getNextPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
976                    } else {
977                            return this.getNextAccrualIntervalDate(earnInterval, aDate);
978                    }
979            }
980            
981            private java.util.Date getNextPayCalIntervalDate(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
982                    if(StringUtils.isNotEmpty(payCalName) 
983                                    && !aMap.isEmpty()
984                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
985                            List<CalendarEntries> entryList = aMap.get(payCalName);
986                            if(CollectionUtils.isNotEmpty(entryList)) {
987                                    for(CalendarEntries anEntry : entryList) {
988                                            // endPeriodDate of calendar entry is the beginning hour of the next day, so we need to substract one day from it to get the real end date
989                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
990                                            if(anEntry.getBeginPeriodDate().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
991                                                    // the endDate of the cal entry that contains the passed in date is the next pay calendar interval date
992                                                    return endDate;
993                                            }
994                                    }
995                            }
996                    }
997                    return aDate;
998            }
999            
1000            @Override
1001            public java.util.Date getNextAccrualIntervalDate(String earnInterval, Date aDate) {
1002                    Calendar aCal = Calendar.getInstance();
1003                    aCal.setTime(aDate);
1004                    
1005                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
1006                            // no change to calendar
1007                    } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
1008                            if(aCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY) {
1009                                    aCal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);      // set to the Saturday of previous week
1010                            } else {
1011                                    aCal.add(Calendar.WEEK_OF_YEAR, 1);
1012                            }
1013                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
1014                            if(aCal.get(Calendar.DAY_OF_MONTH) <=15) {
1015                                    aCal.set(Calendar.DAY_OF_MONTH, 15);
1016                            } else {
1017                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1018                            }
1019                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
1020                            aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1021                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
1022                            aCal.set(Calendar.DAY_OF_YEAR, aCal.getActualMaximum(Calendar.DAY_OF_YEAR));
1023                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
1024                            // no change to calendar
1025                    } 
1026                    return aCal.getTime();
1027            }
1028    
1029            private int getWorkDaysInInterval(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
1030                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
1031                            return this.getWorkDaysInPayCalInterval(aDate, earnInterval, payCalName, aMap);
1032                    } else {
1033                            return this.getWorkDaysInAccrualInterval(earnInterval, aDate);
1034                    }
1035            }
1036            
1037            private int getWorkDaysInPayCalInterval(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
1038                    if(StringUtils.isNotEmpty(payCalName) 
1039                                    && !aMap.isEmpty()
1040                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
1041                            List<CalendarEntries> entryList = aMap.get(payCalName);
1042                            if(CollectionUtils.isNotEmpty(entryList)) {
1043                                    for(CalendarEntries anEntry : entryList) {
1044                                            // endPeriodDate of calendar entry is the beginning hour of the next day, so we need to substract one day from it to get the real end date
1045                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
1046                                            if(anEntry.getBeginPeriodDate().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
1047                                                    return TKUtils.getWorkDays(anEntry.getBeginPeriodDate(), endDate);
1048                                            }
1049                                    }
1050                            }
1051                    }
1052                    return 0;
1053            }
1054            
1055            @Override
1056            public int getWorkDaysInAccrualInterval(String earnInterval, Date aDate) {
1057                    Calendar aCal = Calendar.getInstance();
1058                    aCal.setTime(aDate);
1059                    
1060                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
1061                            return 1;
1062                    } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
1063                            return 5;       
1064                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
1065                            if(aCal.get(Calendar.DAY_OF_MONTH) <= 15) {
1066                                    aCal.set(Calendar.DAY_OF_MONTH, 1);
1067                                    java.util.Date start = aCal.getTime();
1068                                    aCal.set(Calendar.DAY_OF_MONTH, 15);
1069                                    java.util.Date end = aCal.getTime();
1070                                    return TKUtils.getWorkDays(start, end);
1071                            } else {
1072                                    aCal.set(Calendar.DAY_OF_MONTH, 16);
1073                                    java.util.Date start = aCal.getTime();
1074                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1075                                    java.util.Date end = aCal.getTime();
1076                                    return TKUtils.getWorkDays(start, end);
1077                            }
1078                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
1079                            aCal.set(Calendar.DAY_OF_MONTH, 1);
1080                            java.util.Date start = aCal.getTime();
1081                            aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1082                            java.util.Date end = aCal.getTime();
1083                            return TKUtils.getWorkDays(start, end);
1084                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
1085                            aCal.set(Calendar.DAY_OF_YEAR, 1);
1086                            java.util.Date start = aCal.getTime();
1087                            aCal.set(Calendar.DAY_OF_YEAR, aCal.getActualMaximum(Calendar.DAY_OF_YEAR));
1088                            java.util.Date end = aCal.getTime();
1089                            return TKUtils.getWorkDays(start, end);
1090                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
1091                            return 0;
1092                    }               
1093                    return 0;
1094            }
1095            
1096            public java.util.Date getRuleStartDate(String earnInterval, Date serviceDate, Long startAcc) {
1097                    Calendar aCal = Calendar.getInstance();
1098                    aCal.setTime(serviceDate);
1099                    String intervalValue = TkConstants.SERVICE_UNIT_OF_TIME.get(earnInterval);
1100                    int startInt = startAcc.intValue();
1101                    
1102                    if (intervalValue.equals("Months")) {
1103                            aCal.add(Calendar.MONTH, startInt);
1104                            if(aCal.get(Calendar.DAY_OF_MONTH) > aCal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
1105                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1106                            }
1107                    } else if (intervalValue.equals("Years")) {
1108                            aCal.set(Calendar.YEAR, startInt);
1109                    }else {
1110                            // no change to calendar
1111                    }
1112                    return aCal.getTime();
1113            }
1114            
1115            public boolean getProrationFlag(String proration) {
1116                    if(proration == null) {
1117                            return true;
1118                    }
1119                    return proration.equals("Y") ? true : false;
1120            }
1121            
1122            @Override
1123            public boolean statusChangedSinceLastRun(String principalId) {
1124                    PrincipalAccrualRan par = TkServiceLocator.getPrincipalAccrualRanService().getLastPrincipalAccrualRan(principalId);
1125                    if(par == null) {
1126                            return true;
1127                    }
1128                    Job aJob = TkServiceLocator.getJobService().getMaxTimestampJob(principalId);
1129                    
1130                    if(aJob != null && aJob.getTimestamp().after(par.getLastRanTs())) {
1131                            return true;
1132                    }
1133                    
1134                    Assignment anAssign = TkServiceLocator.getAssignmentService().getMaxTimestampAssignment(principalId);
1135                    if(anAssign != null && anAssign.getTimestamp().after(par.getLastRanTs())) {
1136                            return true;
1137                    }
1138                    
1139                    PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getMaxTimeStampPrincipalHRAttributes(principalId);
1140                    if(pha != null && pha.getTimestamp().after(par.getLastRanTs())) {
1141                            return true;
1142                    }
1143                    // if there are leave blocks created for earn codes with eligible-for-accrual = no since the last accrual run, it should trigger recalculation 
1144                    List<LeaveBlock> lbList = TkServiceLocator.getLeaveBlockService().getABELeaveBlocksSinceTime(principalId, par.getLastRanTs());
1145                    if(CollectionUtils.isNotEmpty(lbList)) {
1146                            return true;
1147                    }               
1148                    return false;
1149            }
1150            
1151        public List<AccrualCategoryRule> getAccrualCategoryRulesForDate(List<AccrualCategoryRule> acrList, String accrualCategoryId, java.util.Date currentDate, java.util.Date serviceDate) {
1152            Calendar startCal = new GregorianCalendar();
1153            Calendar endCal = new GregorianCalendar();
1154            List<AccrualCategoryRule> aList = new ArrayList<AccrualCategoryRule>();
1155            if(CollectionUtils.isNotEmpty(acrList)) {
1156                    for(AccrualCategoryRule acr : acrList) {
1157                            if(acr.getLmAccrualCategoryId().equals(accrualCategoryId)) {
1158                                    String uot = acr.getServiceUnitOfTime();
1159                                    int startTime = acr.getStart().intValue();
1160                                            int endTime = acr.getEnd().intValue();
1161                                            
1162                                            startCal.setTime(serviceDate);
1163                                            endCal.setTime(serviceDate);
1164                                    if(uot.equals("M")) {           // monthly
1165                                            startCal.add(Calendar.MONTH, startTime);
1166                                            endCal.add(Calendar.MONTH, endTime);
1167                                            endCal.add(Calendar.DATE, -1);
1168                                    } else if(uot.endsWith("Y")) { // yearly
1169                                            startCal.add(Calendar.YEAR, startTime);
1170                                            endCal.add(Calendar.YEAR, endTime);
1171                                            endCal.add(Calendar.DATE, -1);
1172                                    }
1173                                    
1174                                    // max days in months differ, if the date is bigger than the max day, set it to the max day of the month
1175                                            if(startCal.getActualMaximum(Calendar.DAY_OF_MONTH) < startCal.get(Calendar.DATE)) {
1176                                                    startCal.set(Calendar.DATE, startCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1177                                            }
1178                                            if(endCal.getActualMaximum(Calendar.DAY_OF_MONTH) < endCal.get(Calendar.DATE)) {
1179                                                    endCal.set(Calendar.DATE, endCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1180                                            }
1181                                    
1182                                    if(TKUtils.removeTime(currentDate).compareTo(TKUtils.removeTime(startCal.getTime())) >= 0 
1183                                                    && TKUtils.removeTime(currentDate).compareTo(TKUtils.removeTime(endCal.getTime())) <=0 ) {
1184                                            aList.add(acr);
1185                                    }
1186                            }
1187                    }
1188            }
1189            return aList;
1190            }
1191        
1192        public AccrualCategoryRule getRuleForAccrualCategory(List<AccrualCategoryRule> acrList, AccrualCategory ac) {
1193            if(CollectionUtils.isNotEmpty(acrList)) {
1194                    for(AccrualCategoryRule acr : acrList) {
1195                            if(acr.getLmAccrualCategoryId().equals(ac.getLmAccrualCategoryId())) {
1196                                    return acr;
1197                            }
1198                    }
1199                }
1200            return null;
1201        }
1202    }