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                                    if(!(StringUtils.equals(lb.getLeaveBlockType(),LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER) ||
374                                                    StringUtils.equals(lb.getLeaveBlockType(),LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT)))
375                                            TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(), runAsPrincipalId);
376                            }
377                    }
378                    
379                    for(LeaveBlock accrualLb : sstoAccrualList) {
380                            for(LeaveBlock usageLb : sstoUsageList) {
381                                    // both usage and accrual ssto leave blocks are there, so the ssto accural is not banked, removed both leave blocks
382                                    // 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
383                                    if(accrualLb.getScheduleTimeOffId().equals(usageLb.getScheduleTimeOffId())) {   
384                                            TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(accrualLb.getLmLeaveBlockId(), runAsPrincipalId);
385                                            TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(usageLb.getLmLeaveBlockId(), runAsPrincipalId);
386                                    }
387                            }
388                    }
389                    
390            }
391            
392            private BigDecimal getNotEligibleForAccrualHours(String principalId, Date currentDate) {
393                    BigDecimal hours = BigDecimal.ZERO;
394                    // check if there's any manual not-eligible-for-accrual leave blocks, use the hours of the leave block to adjust accrual calculation 
395                    List<LeaveBlock> lbs = TkServiceLocator.getLeaveBlockService().getNotAccrualGeneratedLeaveBlocksForDate(principalId, currentDate);
396                    for(LeaveBlock lb : lbs) {
397                            EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), currentDate);
398                            if(ec == null) {
399                                    throw new RuntimeException("Cannot find Earn Code for Leave block " + lb.getLmLeaveBlockId());
400                            }
401                            if(ec.getEligibleForAccrual().equals("N") && lb.getLeaveAmount().compareTo(BigDecimal.ZERO) != 0) {
402                                    hours = hours.add(lb.getLeaveAmount());
403                            }               
404                    }
405                    return hours;
406            }
407            
408            private void createLeaveBlock(String principalId, List<LeaveBlock> accrualLeaveBlocks, 
409                            java.util.Date currentDate, BigDecimal hrs, AccrualCategory anAC, String sysSchTimeOffId, 
410                            boolean createZeroLeaveBlock, String leaveDocId) {
411                    // Replacing Leave Code to earn code - KPME 1634
412                    EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(anAC.getEarnCode(), anAC.getEffectiveDate());
413                    if(ec == null) {
414                            throw new RuntimeException("Cannot find Earn Code for Accrual category " + anAC.getAccrualCategory());
415                    }
416                    // use rounding option and fract time allowed of Leave Code to round the leave block hours
417                    BigDecimal roundedHours = TkServiceLocator.getEarnCodeService().roundHrsWithEarnCode(hrs, ec);
418                    if(!createZeroLeaveBlock && roundedHours.compareTo(BigDecimal.ZERO) == 0) {
419                            return; // do not create leave block with zero amount
420                    }
421                    LeaveBlock aLeaveBlock = new LeaveBlock();
422                    aLeaveBlock.setAccrualCategory(anAC.getAccrualCategory());
423                    aLeaveBlock.setLeaveDate(new java.sql.Date(currentDate.getTime()));
424                    aLeaveBlock.setPrincipalId(principalId);
425                    //More than one earn code can be associated with an accrual category. Which one does this get?
426                    aLeaveBlock.setEarnCode(anAC.getEarnCode());
427                    aLeaveBlock.setDateAndTime(new Timestamp(currentDate.getTime()));
428                    aLeaveBlock.setAccrualGenerated(true);
429                    aLeaveBlock.setBlockId(0L);
430                    aLeaveBlock.setScheduleTimeOffId(sysSchTimeOffId);
431                    aLeaveBlock.setLeaveAmount(roundedHours);
432                    aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
433                    aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
434                    aLeaveBlock.setDocumentId(leaveDocId);
435                    
436                    accrualLeaveBlocks.add(aLeaveBlock);
437                    
438            }
439            
440            private void createEmptyLeaveBlockForStatusChange(String principalId, List<LeaveBlock> accrualLeaveBlocks, java.util.Date currentDate) {
441                    LeaveBlock aLeaveBlock = new LeaveBlock();
442                    aLeaveBlock.setAccrualCategory(null);
443                    aLeaveBlock.setLeaveDate(new java.sql.Date(currentDate.getTime()));
444                    aLeaveBlock.setPrincipalId(principalId);
445                    aLeaveBlock.setEarnCode(LMConstants.STATUS_CHANGE_EARN_CODE);   // fake leave code
446                    aLeaveBlock.setDateAndTime(new Timestamp(currentDate.getTime()));
447                    aLeaveBlock.setAccrualGenerated(true);
448                    aLeaveBlock.setBlockId(0L);
449                    aLeaveBlock.setScheduleTimeOffId(null);
450                    aLeaveBlock.setLeaveAmount(BigDecimal.ZERO);
451                    aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
452                    aLeaveBlock.setRequestStatus(LMConstants.REQUEST_STATUS.APPROVED);
453                    
454                    accrualLeaveBlocks.add(aLeaveBlock);
455                    
456            }
457    
458            private void calculateHours(String accrualCategoryId, BigDecimal fte, BigDecimal rate, Map<String, BigDecimal> accumulatedAmounts ) {
459                    BigDecimal hours = rate.multiply(fte);
460                    BigDecimal oldHours = accumulatedAmounts.get(accrualCategoryId);
461                    BigDecimal newHours = oldHours == null ? hours : hours.add(oldHours);
462                    accumulatedAmounts.put(accrualCategoryId, newHours);
463            }
464            
465            public Date getStartAccrualDate(String principalId){
466                    return null;
467            }
468            
469            public Date getEndAccrualDate(String principalId){
470                    //KPME-1246  Fetch planning months
471                    
472                    return null;
473            }
474    
475            @Override
476            public void runAccrual(List<String> principalIds) {
477                    for(String principalId : principalIds){
478                            runAccrual(principalId);
479                    }
480            }
481            
482            private boolean isDateAnIntervalDate(java.util.Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
483                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
484                            return isDateAtPayCalInterval(aDate, earnInterval, payCalName, aMap);
485                    } else {
486                            return this.isDateAtEarnInterval(aDate, earnInterval);
487                    }
488            }
489            
490            private boolean isDateAtPayCalInterval(java.util.Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
491                    if(StringUtils.isNotEmpty(payCalName) 
492                                    && !aMap.isEmpty()
493                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
494                            List<CalendarEntries> entryList = aMap.get(payCalName);
495                            if(CollectionUtils.isNotEmpty(entryList)) {
496                                    for(CalendarEntries anEntry : entryList) {
497                                            // 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
498                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
499                                            if(aDate.compareTo(endDate) == 0) {
500                                                    return true;
501                                            }
502                                    }
503                            }
504                    }
505                    return false;
506            }
507            
508            @Override
509            public boolean isDateAtEarnInterval(java.util.Date aDate, String earnInterval) {
510                    boolean atEarnInterval = false;
511                    if(LMConstants.ACCRUAL_EARN_INTERVAL_MAP.containsKey(earnInterval)) {
512                            Calendar aCal = Calendar.getInstance();
513                            aCal.setTime(aDate);
514                            
515                            if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
516                                    atEarnInterval = true;
517                            } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
518                                    // figure out if the day is a Saturday
519                                    if(aCal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY) {
520                                            atEarnInterval = true;
521                                    }
522                            } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
523                                    // either the 15th or the last day of the month
524                                    if(aCal.get(Calendar.DAY_OF_MONTH) == 15 || aCal.get(Calendar.DAY_OF_MONTH) == aCal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
525                                            atEarnInterval = true;
526                                    }
527                            } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
528                                    // the last day of the month
529                                    if(aCal.get(Calendar.DAY_OF_MONTH) == aCal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
530                                            atEarnInterval = true;
531                                    }
532                            } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
533                                    // the last day of the year
534                                    if(aCal.get(Calendar.DAY_OF_YEAR) == aCal.getActualMaximum(Calendar.DAY_OF_YEAR)) {
535                                            atEarnInterval = true;
536                                    }
537                            }else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
538                                    // no calculation
539                            }
540                    }
541                    return atEarnInterval;
542            }
543    
544    
545            @Override
546            public RateRangeAggregate buildRateRangeAggregate(String principalId, Date startDate, Date endDate) {
547                    RateRangeAggregate rrAggregate = new RateRangeAggregate();
548                    List<RateRange> rateRangeList = new ArrayList<RateRange>(); 
549                    Calendar gc = new GregorianCalendar();
550                    gc.setTime(startDate);
551                    // get all active jobs that are effective before the endDate
552                    List<Job> activeJobs = TkServiceLocator.getJobService().getAllActiveLeaveJobs(principalId, endDate);
553                    List<Job> inactiveJobs = TkServiceLocator.getJobService().getAllInActiveLeaveJobsInRange(principalId, startDate, endDate);
554                    
555                    List<PrincipalHRAttributes> phaList = TkServiceLocator.getPrincipalHRAttributeService().getAllActivePrincipalHrAttributesForPrincipalId(principalId, endDate);
556                    List<PrincipalHRAttributes> inactivePhaList = TkServiceLocator.getPrincipalHRAttributeService().getAllInActivePrincipalHrAttributesForPrincipalId(principalId, endDate);
557                    
558                    if(activeJobs.isEmpty() || phaList.isEmpty()) {
559                            return rrAggregate;
560                    }
561                    
562                    Set<String> phaLpSet = new HashSet<String>();
563                    Set<String> calNameSet = new HashSet<String>();
564                    if(CollectionUtils.isNotEmpty(phaList)) {
565                            for(PrincipalHRAttributes pha : phaList) {
566                                    phaLpSet.add(pha.getLeavePlan());
567                                    calNameSet.add(pha.getPayCalendar());
568                            }
569                    }
570                    
571                    List<LeavePlan> activeLpList = new ArrayList<LeavePlan> ();
572                    List<LeavePlan> inactiveLpList = new ArrayList<LeavePlan> ();
573                    for(String lpString : phaLpSet) {
574                            List<LeavePlan> aList = TkServiceLocator.getLeavePlanService().getAllActiveLeavePlan(lpString, endDate);
575                            activeLpList.addAll(aList);
576                            
577                            aList = TkServiceLocator.getLeavePlanService().getAllInActiveLeavePlan(lpString, endDate);
578                            inactiveLpList.addAll(aList);
579                    }
580                    
581                    // get all pay calendar entries for this employee. used to determine interval dates
582                    Map<String, List<CalendarEntries>> calEntryMap = new HashMap<String, List<CalendarEntries>>();
583                    for(String calName : calNameSet) {
584                            org.kuali.hr.time.calendar.Calendar aCal = TkServiceLocator.getCalendarService().getCalendarByGroup(calName);
585                            if(aCal != null) {
586                                    List<CalendarEntries> aList = TkServiceLocator.getCalendarEntriesService().getAllCalendarEntriesForCalendarId(aCal.getHrCalendarId());
587                                    Collections.sort(aList);
588                                    calEntryMap.put(calName, aList);
589                            }
590                    }
591                    rrAggregate.setCalEntryMap(calEntryMap);                
592                    
593                    Set<String> lpStringSet = new HashSet<String>();
594                    if(CollectionUtils.isNotEmpty(activeLpList)) {
595                            for(LeavePlan lp : activeLpList) {
596                                    lpStringSet.add(lp.getLeavePlan());
597                            }
598                    }
599                    List<SystemScheduledTimeOff> sstoList = new ArrayList<SystemScheduledTimeOff>();
600                    for(String lpString : lpStringSet) {
601                            List<SystemScheduledTimeOff> aList =TkServiceLocator.getSysSchTimeOffService().getSystemScheduledTimeOffsForLeavePlan(startDate, endDate, lpString);
602                            if(CollectionUtils.isNotEmpty(aList)) {
603                                    sstoList.addAll(aList);
604                            }
605                    }
606                    
607                    List<AccrualCategory> activeAccrCatList = new ArrayList<AccrualCategory>();
608                    List<AccrualCategory> inactiveAccrCatList = new ArrayList<AccrualCategory>();
609                    for(String lpString : lpStringSet) {
610                            List<AccrualCategory> aList = TkServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate);
611                            if(CollectionUtils.isNotEmpty(aList)) {
612                                    activeAccrCatList.addAll(aList);
613                            }
614                            
615                            aList = TkServiceLocator.getAccrualCategoryService().getInActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate);
616                            if(CollectionUtils.isNotEmpty(aList)) {
617                                    inactiveAccrCatList.addAll(aList);
618                            }
619                    }
620                    
621                    List<AccrualCategoryRule> activeRuleList = new ArrayList<AccrualCategoryRule>();
622                    List<AccrualCategoryRule> inactiveRuleList = new ArrayList<AccrualCategoryRule>();
623                    for(AccrualCategory ac : activeAccrCatList) {
624                            List<AccrualCategoryRule> aRuleList = TkServiceLocator.getAccrualCategoryRuleService().getActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId(), endDate);
625                            activeRuleList.addAll(aRuleList);
626                            
627                            aRuleList = TkServiceLocator.getAccrualCategoryRuleService().getInActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId(), endDate);
628                            inactiveRuleList.addAll(aRuleList);
629                    }
630                    
631                    List<LeaveCalendarDocumentHeader> lcDocList = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getAllDocumentHeadersInRangeForPricipalId(principalId, startDate, endDate);
632                    
633                    BigDecimal previousFte = null;
634                    List<Job> jobs = new ArrayList<Job>();
635                    
636                while (!gc.getTime().after(endDate)) {
637                    RateRange rateRange = new RateRange();
638                    java.util.Date currentDate = gc.getTime();
639                    
640                    jobs = this.getJobsForDate(activeJobs, inactiveJobs, currentDate);
641                    if(jobs.isEmpty()) {    // no jobs found for this day
642                            gc.add(Calendar.DATE, 1);
643                            continue;
644                    }
645                            rateRange.setJobs(jobs);
646                            
647                            // detect if there's a status change
648                            BigDecimal fteSum = TkServiceLocator.getJobService().getFteSumForJobs(jobs);
649                            rateRange.setAccrualRatePercentageModifier(fteSum);
650                            BigDecimal standardHours = TkServiceLocator.getJobService().getStandardHoursSumForJobs(jobs);
651                            rateRange.setStandardHours(standardHours);
652                            
653                            if(previousFte != null && !previousFte.equals(fteSum)) {
654                                    rateRange.setStatusChanged(true);
655                                    rrAggregate.setRateRangeChanged(true);
656                            }
657                            previousFte = fteSum;
658                            
659                            // figure out the PrincipalHRAttributes for this day
660                            PrincipalHRAttributes phra = this.getPrincipalHrAttributesForDate(phaList, currentDate);
661                            rateRange.setPrincipalHRAttributes(phra);
662                            
663                            if(rateRange.getPrincipalHRAttributes() != null) {
664                                    // figure out if there's an end principalHrAttributes for the initial principalHRAttributes
665                                    PrincipalHRAttributes endPhra = this.getInactivePrincipalHrAttributesForDate(inactivePhaList, rateRange.getPrincipalHRAttributes().getEffectiveDate(), currentDate);
666                                    rateRange.setEndPrincipalHRAttributes(endPhra);
667                            }
668                            
669                            // get leave plan for this day
670                            if(rateRange.getPrincipalHRAttributes()!= null) {                               
671                                    rateRange.setLeavePlan(this.getLeavePlanForDate(activeLpList, inactiveLpList, rateRange.getPrincipalHRAttributes().getLeavePlan(), currentDate));
672                            }
673                            
674                            if(rateRange.getLeavePlan() != null) {
675                                    // get accrual category list for this day
676                                    List<AccrualCategory> acsForDay = this.getAccrualCategoriesForDate(activeAccrCatList, inactiveAccrCatList, rateRange.getLeavePlan().getLeavePlan(), currentDate);
677                                    rateRange.setAcList(acsForDay);
678                                    
679                                    // get System scheduled time off for this day
680                                    for(SystemScheduledTimeOff ssto : sstoList) {
681                                            if(TKUtils.removeTime(ssto.getAccruedDate()).equals(TKUtils.removeTime(currentDate) )
682                                                            && ssto.getLeavePlan().equals(rateRange.getLeavePlan().getLeavePlan())) {
683                                                    // 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
684                                                    // this logic depends on the deactivateOldAccruals() runs before buildRateRangeAggregate()
685                                                    // because deactivateOldAccruals() removes accrued ssto leave blocks unless they are banked/transferred
686                                                    List<LeaveBlock> sstoLbList = TkServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(principalId, ssto.getLmSystemScheduledTimeOffId(), ssto.getAccruedDate());
687                                                    if(CollectionUtils.isEmpty(sstoLbList)) {
688                                                            rateRange.setSysScheTimeOff(ssto);
689                                                    }
690                                            }       
691                                    }
692                            }
693                            // set accrual category rules for the day
694                            if(CollectionUtils.isNotEmpty(rateRange.getAcList())) {
695                                    List<AccrualCategoryRule> rulesForDay = new ArrayList<AccrualCategoryRule>();
696                                    for(AccrualCategory ac : rateRange.getAcList()) {
697                                            rulesForDay.addAll(this.getAccrualCategoryRulesForDate
698                                                                                            (activeRuleList, ac.getLmAccrualCategoryId(), currentDate, rateRange.getPrincipalHRAttributes().getServiceDate()));
699                                    }
700                                    rateRange.setAcRuleList(rulesForDay);
701                            
702                            }
703                            
704                            DateTime beginInterval = new DateTime(gc.getTime());
705                            gc.add(Calendar.DATE, 1);
706                            DateTime endInterval = new DateTime(gc.getTime());
707                            Interval range = new Interval(beginInterval, endInterval);
708                            rateRange.setRange(range);
709                            // assign leave document id to range if there is an existing leave doc for currentDate.
710                            // The doc Id will be assigned to leave blocks created at this rate range
711                            rateRange.setLeaveCalendarDocumentId(this.getLeaveDocumentForDate(lcDocList, currentDate));
712                            rateRangeList.add(rateRange);          
713                }
714                    rrAggregate.setRateRanges(rateRangeList);
715                    rrAggregate.setCurrentRate(null);
716                    return rrAggregate;
717            }
718            
719            private String getLeaveDocumentForDate(List<LeaveCalendarDocumentHeader> lcDocList, java.util.Date currentDate) {
720                    for(LeaveCalendarDocumentHeader lcdh : lcDocList) {
721                            if(!lcdh.getBeginDate().after(currentDate) && lcdh.getEndDate().after(currentDate)) {
722                                    return lcdh.getDocumentId();
723                            }
724                    }
725                    return "";
726            }
727                    
728            public List<Job> getJobsForDate(List<Job> activeJobs, List<Job> inactiveJobs, java.util.Date currentDate) {
729                    List<Job> jobs = new ArrayList<Job>();
730            for(Job aJob : activeJobs) {
731                    if(!aJob.getEffectiveDate().after(currentDate)) {
732                            jobs.add(aJob);
733                    }
734            }
735            if(CollectionUtils.isNotEmpty(jobs)) {
736                    List<Job> tempList = new ArrayList<Job>();
737                    tempList.addAll(jobs);
738                    for(Job aJob : tempList) {
739                            for(Job inactiveJob : inactiveJobs) {
740                                    if(inactiveJob.getJobNumber().equals(aJob.getJobNumber())
741                                            && inactiveJob.getEffectiveDate().after(aJob.getEffectiveDate())
742                                            && !inactiveJob.getEffectiveDate().after(currentDate)) {
743                                                    // remove inactive job from list
744                                                    jobs.remove(aJob);
745                                    }
746                            }
747                    }
748            }
749            return jobs;
750            }
751            
752            public PrincipalHRAttributes getPrincipalHrAttributesForDate(List<PrincipalHRAttributes> activeList, java.util.Date currentDate) {
753                    List<PrincipalHRAttributes> phasForDay = new ArrayList<PrincipalHRAttributes>();
754                    for(PrincipalHRAttributes pha : activeList) {
755                            if(pha != null && pha.getEffectiveDate() != null && pha.getServiceDate() != null
756                                            && !pha.getEffectiveDate().after(currentDate) && !pha.getServiceDate().after(currentDate)) {
757                            phasForDay.add(pha);
758                    }
759                    }
760                    if(CollectionUtils.isNotEmpty(phasForDay)) {
761                            PrincipalHRAttributes pha = phasForDay.get(0);
762                            int indexOfMaxEffDt = 0;
763                            if(phasForDay.size() > 1) {
764                                    for(int i = 1; i < phasForDay.size(); i++) {
765                                            if( (phasForDay.get(i).getEffectiveDate().after(phasForDay.get(indexOfMaxEffDt).getEffectiveDate()))
766                                                            ||(phasForDay.get(i).getEffectiveDate().equals(phasForDay.get(indexOfMaxEffDt).getEffectiveDate())
767                                                                            && phasForDay.get(i).getTimestamp().after(phasForDay.get(indexOfMaxEffDt).getTimestamp()))) {
768                                                    indexOfMaxEffDt = i;
769                                            }
770                                    }
771                                    pha = phasForDay.get(indexOfMaxEffDt);
772                            }
773                            return pha;
774                    }
775                    return null;
776            }
777            
778            public PrincipalHRAttributes getInactivePrincipalHrAttributesForDate(List<PrincipalHRAttributes> inactiveList, java.util.Date activeDate, java.util.Date currentDate) {
779                    List<PrincipalHRAttributes> inactivePhasForDay = new ArrayList<PrincipalHRAttributes>();
780                    for(PrincipalHRAttributes pha : inactiveList) {
781                            if( pha.getEffectiveDate().after(activeDate) && !pha.getServiceDate().after(currentDate)) {
782                                    inactivePhasForDay.add(pha);
783                    }
784                    }
785                    if(CollectionUtils.isNotEmpty(inactivePhasForDay)) {
786                            PrincipalHRAttributes pha = inactivePhasForDay.get(0);
787                            int indexOfMaxEffDt = 0;
788                            if(inactivePhasForDay.size() > 1) {
789                                    for(int i = 1; i < inactivePhasForDay.size(); i++) {
790                                            if( (inactivePhasForDay.get(i).getEffectiveDate().after(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveDate()))
791                                                            ||(inactivePhasForDay.get(i).getEffectiveDate().equals(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveDate())
792                                                                            && inactivePhasForDay.get(i).getTimestamp().after(inactivePhasForDay.get(indexOfMaxEffDt).getTimestamp()))) {
793                                                    indexOfMaxEffDt = i;
794                                            }
795                                    }
796                                    pha = inactivePhasForDay.get(indexOfMaxEffDt);
797                            }
798                            return pha;
799                    }
800                    return null;
801            }
802            
803            public LeavePlan getLeavePlanForDate(List<LeavePlan> activeLpList, List<LeavePlan> inactiveLpList, String leavePlan, java.util.Date currentDate) {
804                    List<LeavePlan> lpsForDay = new ArrayList<LeavePlan>();
805                    for(LeavePlan lp : activeLpList) {
806                            if(lp.getLeavePlan().equals(leavePlan) && !lp.getEffectiveDate().after(currentDate)) {
807                                    lpsForDay.add(lp);
808                            }
809                    }
810                    List<LeavePlan> aList = new ArrayList<LeavePlan>();
811                    aList.addAll(lpsForDay);
812            for(LeavePlan lp : aList) {
813                    for(LeavePlan inactiveLp : inactiveLpList) {
814                            if(inactiveLp.getLeavePlan().equals(lp.getLeavePlan())
815                                    && inactiveLp.getEffectiveDate().after(lp.getEffectiveDate())
816                                    && !inactiveLp.getEffectiveDate().after(currentDate)) {
817                                            // remove inactive leave plan from list
818                                            lpsForDay.remove(lp);
819                            }
820                    }
821            }
822                    if(CollectionUtils.isNotEmpty(lpsForDay)) {
823                            LeavePlan aLp = lpsForDay.get(0);
824                            int indexOfMaxEffDt = 0;
825                            if(lpsForDay.size() > 1) {
826                                    for(int i = 1; i < lpsForDay.size(); i++) {
827                                            if( (lpsForDay.get(i).getEffectiveDate().after(lpsForDay.get(indexOfMaxEffDt).getEffectiveDate()))
828                                                            ||(lpsForDay.get(i).getEffectiveDate().equals(lpsForDay.get(indexOfMaxEffDt).getEffectiveDate())
829                                                                            && lpsForDay.get(i).getTimestamp().after(lpsForDay.get(indexOfMaxEffDt).getTimestamp()))) {
830                                                    indexOfMaxEffDt = i;
831                                            }
832                                    }
833                                    aLp = lpsForDay.get(indexOfMaxEffDt);
834                            }
835                            return aLp;
836                    }
837                    return null;
838            }
839            
840            public List<AccrualCategory> getAccrualCategoriesForDate(List<AccrualCategory> activeAccrCatList, List<AccrualCategory> inactiveAccrCatList, String leavePlan, java.util.Date currentDate) {
841                    Set<AccrualCategory> aSet = new HashSet<AccrualCategory>();
842                    for(AccrualCategory ac : activeAccrCatList) {
843                            if(ac.getLeavePlan().equals(leavePlan) && !ac.getEffectiveDate().after(currentDate)) {
844                                    aSet.add(ac);
845                            }
846                    }
847                    List<AccrualCategory> list1 = new ArrayList<AccrualCategory>();
848                    list1.addAll(aSet);
849            for(AccrualCategory ac : list1) {
850                    for(AccrualCategory inactiveAc : inactiveAccrCatList) {
851                            if(inactiveAc.getAccrualCategory().equals(ac.getAccrualCategory())
852                                    && inactiveAc.getEffectiveDate().after(ac.getEffectiveDate())
853                                    && !inactiveAc.getEffectiveDate().after(currentDate)) {
854                                            // remove inactive accrual category from list
855                                    aSet.remove(ac);
856                            }
857                    }
858            }
859            List<AccrualCategory> acsForDay = new ArrayList<AccrualCategory>();
860            acsForDay.addAll(aSet);
861            return acsForDay;       
862            }
863            
864            @Override
865            public boolean isEmpoyeementFutureStatusChanged(String principalId, Date startDate, Date endDate) {
866                    Date currentDate = TKUtils.getCurrentDate();
867                    if(endDate.after(currentDate)) {
868                            RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate);
869                            if(rrAggregate.isRateRangeChanged()) {
870                                    return true;
871                            }
872                    }
873                    return false;
874            }
875            
876            @Override
877            public void calculateFutureAccrualUsingPlanningMonth(String principalId, Date asOfDate) {
878                    PrincipalHRAttributes phra = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, asOfDate);
879                    if(phra != null) {
880                            // use the date from pay period to get the leave plan
881                            LeavePlan lp = TkServiceLocator.getLeavePlanService().getLeavePlan(phra.getLeavePlan(), asOfDate);  
882                            if(lp != null && StringUtils.isNotEmpty(lp.getPlanningMonths())) {
883                                    Calendar aCal = Calendar.getInstance();
884                                    // go back a year 
885                                    aCal.setTime(asOfDate);
886                                    aCal.add(Calendar.YEAR, -1);
887                                    if(aCal.getActualMaximum(Calendar.DAY_OF_MONTH) < aCal.get(Calendar.DATE)) {
888                                            aCal.set(Calendar.DATE, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
889                                    }
890                                    Date startDate = new java.sql.Date(aCal.getTime().getTime());
891                                    // go forward using planning months
892                                    aCal.setTime(asOfDate);
893                                    aCal.add(Calendar.MONTH, Integer.parseInt(lp.getPlanningMonths()));
894                                    // max days in months differ, if the date is bigger than the max day, set it to the max day of the month
895                                    if(aCal.getActualMaximum(Calendar.DAY_OF_MONTH) < aCal.get(Calendar.DATE)) {
896                                            aCal.set(Calendar.DATE, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
897                                    }
898                                    Date endDate = new java.sql.Date(aCal.getTime().getTime());
899                                    TkServiceLocator.getLeaveAccrualService().runAccrual(principalId, startDate, endDate, true);
900                            }
901                    }
902            }
903            
904            private boolean minimumPercentageReachedForPayPeriod(BigDecimal min, String earnInterval, int workDays, Date intervalDate, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
905                    if(min == null || min.compareTo(BigDecimal.ZERO) == 0) {
906                            return true;
907                    }
908                    int daysInInterval = this.getWorkDaysInInterval(intervalDate, earnInterval, payCalName, aMap);
909                    if(daysInInterval == 0) {
910                            return true;
911                    }
912                    BigDecimal actualPercentage =  new BigDecimal(workDays).divide(new BigDecimal(daysInInterval), 2, BigDecimal.ROUND_HALF_EVEN);
913                    if(actualPercentage.compareTo(min) >= 0) {
914                            return true;
915                    }
916                    
917                    return false;   
918            }
919    
920            private java.util.Date getPrevIntervalDate(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
921                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
922                            return this.getPrevPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
923                    } else {
924                            return this.getPreviousAccrualIntervalDate(earnInterval, aDate);
925                    }
926            }
927            
928            @Override
929            public java.util.Date getPreviousAccrualIntervalDate(String earnInterval, Date aDate) {
930                    Calendar aCal = Calendar.getInstance();
931                    aCal.setTime(aDate);
932    
933                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
934                            aCal.add(Calendar.DAY_OF_YEAR, -1);
935                    } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
936                            aCal.add(Calendar.WEEK_OF_YEAR, -1);
937                            aCal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);      // set to the Saturday of previous week
938                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
939                            aCal.add(Calendar.DAY_OF_YEAR, -15);
940                            if(aCal.get(Calendar.DAY_OF_MONTH) <=15) {
941                                    aCal.set(Calendar.DAY_OF_MONTH, 15);
942                            } else {
943                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
944                            }
945                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
946                            aCal.add(Calendar.MONTH, -1);
947                            aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
948                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
949                            aCal.add(Calendar.YEAR, -1);
950                            aCal.set(Calendar.DAY_OF_YEAR, aCal.getActualMaximum(Calendar.DAY_OF_YEAR));
951                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
952                            // no change to calendar
953                    } 
954                    return aCal.getTime();
955            }
956            
957            private java.util.Date getPrevPayCalIntervalDate(java.util.Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
958                    if(StringUtils.isNotEmpty(payCalName) 
959                                    && !aMap.isEmpty()
960                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
961                            List<CalendarEntries> entryList = aMap.get(payCalName);
962                            if(CollectionUtils.isNotEmpty(entryList)) {
963                                    for(CalendarEntries anEntry : entryList) {
964                                            // 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
965                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
966                                            if(anEntry.getBeginPeriodDate().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
967                                                    // the day before the beginning date of the cal entry that contains the passed in date is the endDate of previous calendar entry
968                                                    java.util.Date prevIntvDate = TKUtils.addDates(anEntry.getBeginPeriodDate(), -1);
969                                                    return prevIntvDate;
970                                            }
971                                    }
972                            }
973                    }
974                    return aDate;
975            }
976            
977            private java.util.Date getNextIntervalDate(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
978                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
979                            return this.getNextPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
980                    } else {
981                            return this.getNextAccrualIntervalDate(earnInterval, aDate);
982                    }
983            }
984            
985            private java.util.Date getNextPayCalIntervalDate(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
986                    if(StringUtils.isNotEmpty(payCalName) 
987                                    && !aMap.isEmpty()
988                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
989                            List<CalendarEntries> entryList = aMap.get(payCalName);
990                            if(CollectionUtils.isNotEmpty(entryList)) {
991                                    for(CalendarEntries anEntry : entryList) {
992                                            // 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
993                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
994                                            if(anEntry.getBeginPeriodDate().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
995                                                    // the endDate of the cal entry that contains the passed in date is the next pay calendar interval date
996                                                    return endDate;
997                                            }
998                                    }
999                            }
1000                    }
1001                    return aDate;
1002            }
1003            
1004            @Override
1005            public java.util.Date getNextAccrualIntervalDate(String earnInterval, Date aDate) {
1006                    Calendar aCal = Calendar.getInstance();
1007                    aCal.setTime(aDate);
1008                    
1009                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
1010                            // no change to calendar
1011                    } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
1012                            if(aCal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY) {
1013                                    aCal.set(Calendar.DAY_OF_WEEK, Calendar.SATURDAY);      // set to the Saturday of previous week
1014                            } else {
1015                                    aCal.add(Calendar.WEEK_OF_YEAR, 1);
1016                            }
1017                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
1018                            if(aCal.get(Calendar.DAY_OF_MONTH) <=15) {
1019                                    aCal.set(Calendar.DAY_OF_MONTH, 15);
1020                            } else {
1021                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1022                            }
1023                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
1024                            aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1025                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
1026                            aCal.set(Calendar.DAY_OF_YEAR, aCal.getActualMaximum(Calendar.DAY_OF_YEAR));
1027                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
1028                            // no change to calendar
1029                    } 
1030                    return aCal.getTime();
1031            }
1032    
1033            private int getWorkDaysInInterval(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
1034                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {
1035                            return this.getWorkDaysInPayCalInterval(aDate, earnInterval, payCalName, aMap);
1036                    } else {
1037                            return this.getWorkDaysInAccrualInterval(earnInterval, aDate);
1038                    }
1039            }
1040            
1041            private int getWorkDaysInPayCalInterval(Date aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntries>> aMap) {
1042                    if(StringUtils.isNotEmpty(payCalName) 
1043                                    && !aMap.isEmpty()
1044                                    && earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.PAY_CAL)) {       // only used for ac earn interval == pay calendar
1045                            List<CalendarEntries> entryList = aMap.get(payCalName);
1046                            if(CollectionUtils.isNotEmpty(entryList)) {
1047                                    for(CalendarEntries anEntry : entryList) {
1048                                            // 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
1049                                            java.util.Date endDate = TKUtils.addDates(anEntry.getEndPeriodDate(), -1);
1050                                            if(anEntry.getBeginPeriodDate().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
1051                                                    return TKUtils.getWorkDays(anEntry.getBeginPeriodDate(), endDate);
1052                                            }
1053                                    }
1054                            }
1055                    }
1056                    return 0;
1057            }
1058            
1059            @Override
1060            public int getWorkDaysInAccrualInterval(String earnInterval, Date aDate) {
1061                    Calendar aCal = Calendar.getInstance();
1062                    aCal.setTime(aDate);
1063                    
1064                    if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.DAILY)) {
1065                            return 1;
1066                    } else if(earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.WEEKLY)) {
1067                            return 5;       
1068                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.SEMI_MONTHLY)) {
1069                            if(aCal.get(Calendar.DAY_OF_MONTH) <= 15) {
1070                                    aCal.set(Calendar.DAY_OF_MONTH, 1);
1071                                    java.util.Date start = aCal.getTime();
1072                                    aCal.set(Calendar.DAY_OF_MONTH, 15);
1073                                    java.util.Date end = aCal.getTime();
1074                                    return TKUtils.getWorkDays(start, end);
1075                            } else {
1076                                    aCal.set(Calendar.DAY_OF_MONTH, 16);
1077                                    java.util.Date start = aCal.getTime();
1078                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1079                                    java.util.Date end = aCal.getTime();
1080                                    return TKUtils.getWorkDays(start, end);
1081                            }
1082                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.MONTHLY)) {
1083                            aCal.set(Calendar.DAY_OF_MONTH, 1);
1084                            java.util.Date start = aCal.getTime();
1085                            aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1086                            java.util.Date end = aCal.getTime();
1087                            return TKUtils.getWorkDays(start, end);
1088                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.YEARLY)) {
1089                            aCal.set(Calendar.DAY_OF_YEAR, 1);
1090                            java.util.Date start = aCal.getTime();
1091                            aCal.set(Calendar.DAY_OF_YEAR, aCal.getActualMaximum(Calendar.DAY_OF_YEAR));
1092                            java.util.Date end = aCal.getTime();
1093                            return TKUtils.getWorkDays(start, end);
1094                    } else if (earnInterval.equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
1095                            return 0;
1096                    }               
1097                    return 0;
1098            }
1099            
1100            public java.util.Date getRuleStartDate(String earnInterval, Date serviceDate, Long startAcc) {
1101                    Calendar aCal = Calendar.getInstance();
1102                    aCal.setTime(serviceDate);
1103                    String intervalValue = TkConstants.SERVICE_UNIT_OF_TIME.get(earnInterval);
1104                    int startInt = startAcc.intValue();
1105                    
1106                    if (intervalValue.equals("Months")) {
1107                            aCal.add(Calendar.MONTH, startInt);
1108                            if(aCal.get(Calendar.DAY_OF_MONTH) > aCal.getActualMaximum(Calendar.DAY_OF_MONTH)) {
1109                                    aCal.set(Calendar.DAY_OF_MONTH, aCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1110                            }
1111                    } else if (intervalValue.equals("Years")) {
1112                            aCal.set(Calendar.YEAR, startInt);
1113                    }else {
1114                            // no change to calendar
1115                    }
1116                    return aCal.getTime();
1117            }
1118            
1119            public boolean getProrationFlag(String proration) {
1120                    if(proration == null) {
1121                            return true;
1122                    }
1123                    return proration.equals("Y") ? true : false;
1124            }
1125            
1126            @Override
1127            public boolean statusChangedSinceLastRun(String principalId) {
1128                    PrincipalAccrualRan par = TkServiceLocator.getPrincipalAccrualRanService().getLastPrincipalAccrualRan(principalId);
1129                    if(par == null) {
1130                            return true;
1131                    }
1132                    Job aJob = TkServiceLocator.getJobService().getMaxTimestampJob(principalId);
1133                    
1134                    if(aJob != null && aJob.getTimestamp().after(par.getLastRanTs())) {
1135                            return true;
1136                    }
1137                    
1138                    Assignment anAssign = TkServiceLocator.getAssignmentService().getMaxTimestampAssignment(principalId);
1139                    if(anAssign != null && anAssign.getTimestamp().after(par.getLastRanTs())) {
1140                            return true;
1141                    }
1142                    
1143                    PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getMaxTimeStampPrincipalHRAttributes(principalId);
1144                    if(pha != null && pha.getTimestamp().after(par.getLastRanTs())) {
1145                            return true;
1146                    }
1147                    // if there are leave blocks created for earn codes with eligible-for-accrual = no since the last accrual run, it should trigger recalculation 
1148                    List<LeaveBlock> lbList = TkServiceLocator.getLeaveBlockService().getABELeaveBlocksSinceTime(principalId, par.getLastRanTs());
1149                    if(CollectionUtils.isNotEmpty(lbList)) {
1150                            return true;
1151                    }               
1152                    return false;
1153            }
1154            
1155        public List<AccrualCategoryRule> getAccrualCategoryRulesForDate(List<AccrualCategoryRule> acrList, String accrualCategoryId, java.util.Date currentDate, java.util.Date serviceDate) {
1156            Calendar startCal = new GregorianCalendar();
1157            Calendar endCal = new GregorianCalendar();
1158            List<AccrualCategoryRule> aList = new ArrayList<AccrualCategoryRule>();
1159            if(CollectionUtils.isNotEmpty(acrList)) {
1160                    for(AccrualCategoryRule acr : acrList) {
1161                            if(acr.getLmAccrualCategoryId().equals(accrualCategoryId)) {
1162                                    String uot = acr.getServiceUnitOfTime();
1163                                    int startTime = acr.getStart().intValue();
1164                                            int endTime = acr.getEnd().intValue();
1165                                            
1166                                            startCal.setTime(serviceDate);
1167                                            endCal.setTime(serviceDate);
1168                                    if(uot.equals("M")) {           // monthly
1169                                            startCal.add(Calendar.MONTH, startTime);
1170                                            endCal.add(Calendar.MONTH, endTime);
1171                                            endCal.add(Calendar.DATE, -1);
1172                                    } else if(uot.endsWith("Y")) { // yearly
1173                                            startCal.add(Calendar.YEAR, startTime);
1174                                            endCal.add(Calendar.YEAR, endTime);
1175                                            endCal.add(Calendar.DATE, -1);
1176                                    }
1177                                    
1178                                    // max days in months differ, if the date is bigger than the max day, set it to the max day of the month
1179                                            if(startCal.getActualMaximum(Calendar.DAY_OF_MONTH) < startCal.get(Calendar.DATE)) {
1180                                                    startCal.set(Calendar.DATE, startCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1181                                            }
1182                                            if(endCal.getActualMaximum(Calendar.DAY_OF_MONTH) < endCal.get(Calendar.DATE)) {
1183                                                    endCal.set(Calendar.DATE, endCal.getActualMaximum(Calendar.DAY_OF_MONTH));
1184                                            }
1185                                    
1186                                    if(TKUtils.removeTime(currentDate).compareTo(TKUtils.removeTime(startCal.getTime())) >= 0 
1187                                                    && TKUtils.removeTime(currentDate).compareTo(TKUtils.removeTime(endCal.getTime())) <=0 ) {
1188                                            aList.add(acr);
1189                                    }
1190                            }
1191                    }
1192            }
1193            return aList;
1194            }
1195        
1196        public AccrualCategoryRule getRuleForAccrualCategory(List<AccrualCategoryRule> acrList, AccrualCategory ac) {
1197            if(CollectionUtils.isNotEmpty(acrList)) {
1198                    for(AccrualCategoryRule acr : acrList) {
1199                            if(acr.getLmAccrualCategoryId().equals(ac.getLmAccrualCategoryId())) {
1200                                    return acr;
1201                            }
1202                    }
1203                }
1204            return null;
1205        }
1206    }