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