org.kuali.kpme.tklm.leave.accrual.service.AccrualServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.kuali.kpme.tklm.leave.accrual.service.AccrualServiceImpl.java

Source

/**
 * Copyright 2004-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.kpme.tklm.leave.accrual.service;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.joda.time.DateTimeConstants;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.kuali.kpme.core.api.accrualcategory.AccrualCategory;
import org.kuali.kpme.core.api.accrualcategory.AccrualCategoryContract;
import org.kuali.kpme.core.api.accrualcategory.AccrualEarnInterval;
import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRule;
import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRuleContract;
import org.kuali.kpme.core.api.assignment.Assignment;
import org.kuali.kpme.core.api.calendar.Calendar;
import org.kuali.kpme.core.api.calendar.entry.CalendarEntry;
import org.kuali.kpme.core.api.earncode.EarnCodeContract;
import org.kuali.kpme.core.api.job.Job;
import org.kuali.kpme.core.api.job.JobContract;
import org.kuali.kpme.core.api.leaveplan.LeavePlan;
import org.kuali.kpme.core.api.leaveplan.LeavePlanContract;
import org.kuali.kpme.core.api.principal.PrincipalHRAttributes;
import org.kuali.kpme.core.service.HrServiceLocator;
import org.kuali.kpme.core.util.HrConstants;
import org.kuali.kpme.core.util.HrContext;
import org.kuali.kpme.core.util.TKUtils;
import org.kuali.kpme.tklm.api.leave.accrual.AccrualService;
import org.kuali.kpme.tklm.api.leave.accrual.PrincipalAccrualRanContract;
import org.kuali.kpme.tklm.api.leave.accrual.RateRangeContract;
import org.kuali.kpme.tklm.api.leave.block.LeaveBlock;
import org.kuali.kpme.tklm.api.leave.timeoff.SystemScheduledTimeOffContract;
import org.kuali.kpme.tklm.common.LMConstants;
import org.kuali.kpme.tklm.leave.accrual.RateRange;
import org.kuali.kpme.tklm.leave.accrual.RateRangeAggregate;
import org.kuali.kpme.tklm.leave.block.LeaveBlockBo;
import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
import org.kuali.kpme.tklm.leave.workflow.LeaveCalendarDocumentHeader;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class AccrualServiceImpl implements AccrualService {
    private static final Logger LOG = Logger.getLogger(AccrualServiceImpl.class);

    @Override
    public void runAccrual(String principalId) {
        DateTime startDate = getStartAccrualDate(principalId);
        DateTime endDate = getEndAccrualDate(principalId);

        LOG.info("AccrualServiceImpl.runAccrual() STARTED with Principal: " + principalId);
        runAccrual(principalId, startDate, endDate, true);

    }

    @Override
    public void runAccrual(String principalId, DateTime startDate, DateTime endDate, boolean recordRanData) {
        runAccrual(principalId, startDate, endDate, recordRanData, HrContext.getPrincipalId());
    }

    @SuppressWarnings("unchecked")
    @Override
    public void runAccrual(String principalId, DateTime startDate, DateTime endDate, boolean recordRanData,
            String runAsPrincipalId) {
        List<LeaveBlock> accrualLeaveBlocks = new ArrayList<LeaveBlock>();
        Map<String, BigDecimal> accumulatedAccrualCatToAccrualAmounts = new HashMap<String, BigDecimal>();
        Map<String, BigDecimal> accumulatedAccrualCatToNegativeAccrualAmounts = new HashMap<String, BigDecimal>();

        if (startDate != null && endDate != null) {
            LOG.info("AccrualServiceImpl.runAccrual() STARTED with Principal: " + principalId + " Start: "
                    + startDate.toString() + " End: " + endDate.toString());
        }
        if (startDate.isAfter(endDate)) {
            LOG.error("Start Date " + startDate.toString() + " should not be later than End Date "
                    + endDate.toString());
            return;
            //         throw new RuntimeException("Start Date " + startDate.toString() + " should not be later than End Date " + endDate.toString());
        }
        //Inactivate all previous accrual-generated entries for this span of time
        deactivateOldAccruals(principalId, startDate, endDate, runAsPrincipalId);

        //Build a rate range aggregate with appropriate information for this period of time detailing Rate Ranges for job
        //entries for this range of time
        RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate);
        PrincipalHRAttributes phra = null;
        PrincipalHRAttributes endPhra = null;
        LeavePlanContract lp = null;
        List<? extends AccrualCategoryContract> accrCatList = null;

        //Iterate over every day in span 
        DateTime currentDate = startDate;
        while (!currentDate.isAfter(endDate)) {
            RateRange currentRange = rrAggregate.getRateOnDate(currentDate);
            if (currentRange == null) {
                currentDate = currentDate.plusDays(1);
                continue;
            }

            phra = currentRange.getPrincipalHRAttributes();
            if (phra == null || currentDate.toLocalDate().isBefore(phra.getServiceLocalDate())) {
                currentDate = currentDate.plusDays(1);
                continue;
            }

            // use the effectiveDate of this principalHRAttribute to search for inactive entries for this principalId
            // If there's an inactive entry, it means the job is going to end on the effectiveDate of the inactive entry
            // used for minimumPercentage and proration
            endPhra = currentRange.getEndPrincipalHRAttributes();
            if (endPhra != null && currentDate.toLocalDate().isAfter(endPhra.getEffectiveLocalDate())) {
                currentDate = currentDate.plusDays(1);
                continue;
            }

            // if the date range is before the service date of this employee, do not calculate accrual
            if (endDate.toLocalDate().isBefore(phra.getServiceLocalDate())) {
                return;
            }
            lp = currentRange.getLeavePlan();
            accrCatList = currentRange.getAcList();
            // if the employee status is changed, create an empty leave block on the currentDate
            if (currentRange.isStatusChanged()) {
                this.createEmptyLeaveBlockForStatusChange(principalId, accrualLeaveBlocks,
                        currentDate.toLocalDate());
            }
            // if no job found for the employee on the currentDate, do nothing
            if (CollectionUtils.isEmpty(currentRange.getJobs())) {
                currentDate = currentDate.plusDays(1);
                continue;
            }

            BigDecimal ftePercentage = currentRange.getAccrualRatePercentageModifier();
            BigDecimal totalOfStandardHours = currentRange.getStandardHours();
            boolean fullFteGranted = false;
            for (AccrualCategoryContract anAC : accrCatList) {
                if (anAC == null)
                    continue;

                fullFteGranted = false;
                if (!currentDate.toLocalDate().isBefore(phra.getEffectiveLocalDate())
                        && !anAC.getAccrualEarnInterval().equals("N")) { // "N" means no accrual
                    boolean prorationFlag = this.isProrationFlag(anAC.getProration());
                    // get the accrual rule 
                    AccrualCategoryRuleContract currentAcRule = this
                            .getRuleForAccrualCategory(currentRange.getAcRuleList(), anAC);

                    // check if accrual category rule changed
                    if (currentAcRule != null) {
                        DateTime ruleStartDate = getRuleStartDate(currentAcRule.getServiceUnitOfTime(),
                                phra.getServiceLocalDate(), currentAcRule.getStart());
                        DateTime previousIntervalDay = this.getPrevIntervalDate(ruleStartDate,
                                anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
                        DateTime nextIntervalDay = this.getNextIntervalDate(ruleStartDate,
                                anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());

                        RateRangeContract previousRange = rrAggregate.getRateOnDate(previousIntervalDay);
                        AccrualCategoryRuleContract previousAcRule = null;
                        if (previousRange != null) {
                            previousAcRule = this.getRuleForAccrualCategory(previousRange.getAcRuleList(), anAC);
                        }
                        // rule changed
                        if (previousAcRule != null && !previousAcRule.getLmAccrualCategoryRuleId()
                                .equals(currentAcRule.getLmAccrualCategoryRuleId())) {
                            if (currentDate.toLocalDate().compareTo(previousIntervalDay.toLocalDate()) >= 0
                                    && currentDate.toLocalDate().compareTo(nextIntervalDay.toLocalDate()) <= 0) {
                                int workDaysInBetween = TKUtils.getWorkDays(ruleStartDate, nextIntervalDay);
                                boolean minReachedFlag = minimumPercentageReachedForPayPeriod(
                                        anAC.getMinPercentWorked(), anAC.getAccrualEarnInterval(),
                                        workDaysInBetween, nextIntervalDay, phra.getPayCalendar(),
                                        rrAggregate.getCalEntryMap());
                                if (prorationFlag) {
                                    if (minReachedFlag) {
                                        // min reached, proration=true, rule changed, then use actual work days of currentRule for calculation
                                        // so nothing special needs to be done here                        
                                    } else {
                                        //minimum percentage NOT reached, proration = true, rule changed, then use previousRule for the whole pay period
                                        currentAcRule = previousAcRule;
                                    }
                                } else {
                                    if (minReachedFlag) {
                                        // min reached, proration = false, rule changed, then accrual the whole fte of the new rule for this pay interval
                                        accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(),
                                                currentAcRule.getAccrualRate());
                                        fullFteGranted = true;
                                    } else {
                                        //min NOT reached, proration = false, rule changed, then accrual the whole fte of the previous rule for this pay interval
                                        accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(),
                                                previousAcRule.getAccrualRate());
                                        fullFteGranted = true;
                                    }
                                }
                            }
                        }
                    }

                    // check for first pay period of principal attributes considering minimum percentage and proration   
                    DateTime firstIntervalDate = this.getNextIntervalDate(
                            phra.getEffectiveLocalDate().toDateTimeAtStartOfDay(), anAC.getAccrualEarnInterval(),
                            phra.getPayCalendar(), rrAggregate.getCalEntryMap());
                    if (!currentDate.toLocalDate().isBefore(phra.getEffectiveLocalDate())
                            && !currentDate.toLocalDate().isAfter(firstIntervalDate.toLocalDate())) {
                        int workDaysInBetween = TKUtils.getWorkDays(
                                phra.getEffectiveLocalDate().toDateTimeAtStartOfDay(), firstIntervalDate);
                        boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(),
                                anAC.getAccrualEarnInterval(), workDaysInBetween, firstIntervalDate,
                                phra.getPayCalendar(), rrAggregate.getCalEntryMap());

                        if (prorationFlag) {
                            if (minReachedFlag) {
                                // minimum reached, proration = true, first pay period, then use actual work days of currentRule for calculation
                                // so nothing special needs to be done here
                            } else {
                                // min NOT reached, proration = true, first pay period, then no accrual for this pay period
                                accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                                accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                                continue;
                            }
                        } else {
                            if (minReachedFlag && currentAcRule != null) {
                                //  minimum reached, proration = false, first pay period, then accrual the whole fte of current AC rule for this pay interval
                                accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(),
                                        currentAcRule.getAccrualRate());
                                fullFteGranted = true;
                            } else {
                                // min NOT reached, proration = false, first pay period, then no accrual for this pay period
                                accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                                accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                                continue;
                            }
                        }
                    }
                    // last accrual interval
                    if (endPhra != null) { // the employment is going to end on the effectiveDate of enPhra
                        DateTime previousIntervalDate = this.getPrevIntervalDate(
                                endPhra.getEffectiveLocalDate().toDateTimeAtStartOfDay(),
                                anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
                        // currentDate is between the end date and the last interval date, so we are in the last interval
                        if (!currentDate.toLocalDate().isAfter(endPhra.getEffectiveLocalDate())
                                && currentDate.toLocalDate().isAfter(previousIntervalDate.toLocalDate())) {
                            DateTime lastIntervalDate = this.getNextIntervalDate(
                                    endPhra.getEffectiveLocalDate().toDateTimeAtStartOfDay(),
                                    anAC.getAccrualEarnInterval(), phra.getPayCalendar(),
                                    rrAggregate.getCalEntryMap());
                            int workDaysInBetween = TKUtils.getWorkDays(previousIntervalDate,
                                    endPhra.getEffectiveLocalDate().toDateTimeAtStartOfDay());
                            boolean minReachedFlag = minimumPercentageReachedForPayPeriod(
                                    anAC.getMinPercentWorked(), anAC.getAccrualEarnInterval(), workDaysInBetween,
                                    lastIntervalDate, phra.getPayCalendar(), rrAggregate.getCalEntryMap());
                            if (prorationFlag) {
                                if (minReachedFlag) {
                                    // minimum reached, proration = true, first pay period, then use actual work days of currentRule for calculation
                                    // so nothing special needs to be done here
                                } else {
                                    // min NOT reached, proration = true, first pay period, then no accrual for this pay period
                                    accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                                    accumulatedAccrualCatToNegativeAccrualAmounts
                                            .remove(anAC.getLmAccrualCategoryId());
                                    continue;
                                }
                            } else {
                                if (minReachedFlag) {
                                    //  minimum reached, proration = false, first pay period, then accrual the whole fte of current AC rule for this pay interval
                                    accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(),
                                            currentAcRule.getAccrualRate());
                                    fullFteGranted = true;
                                } else {
                                    // min NOT reached, proration = false, first pay period, then no accrual for this pay period
                                    accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                                    accumulatedAccrualCatToNegativeAccrualAmounts
                                            .remove(anAC.getLmAccrualCategoryId());
                                    continue;
                                }
                            }
                        }
                    }

                    if (currentAcRule == null) {
                        accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                        accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
                        continue;
                    }

                    //Fetch the accrual rate based on rate range for today(Rate range is the accumulated list of jobs and accrual rate for today)
                    BigDecimal accrualRate = currentAcRule.getAccrualRate();
                    int numberOfWorkDays = this.getWorkDaysInInterval(currentDate, anAC.getAccrualEarnInterval(),
                            phra.getPayCalendar(), rrAggregate.getCalEntryMap());
                    BigDecimal dayRate = numberOfWorkDays > 0
                            ? accrualRate.divide(new BigDecimal(numberOfWorkDays), 6, BigDecimal.ROUND_HALF_UP)
                            : new BigDecimal(0);

                    //get not eligible for accrual hours based on leave block on this day
                    BigDecimal noAccrualHours = getNotEligibleForAccrualHours(principalId,
                            currentDate.toLocalDate());
                    if (noAccrualHours != null && noAccrualHours.compareTo(BigDecimal.ZERO) != 0
                            && totalOfStandardHours.compareTo(BigDecimal.ZERO) != 0) {
                        BigDecimal dayHours = totalOfStandardHours.divide(new BigDecimal(5), 6,
                                BigDecimal.ROUND_HALF_UP);
                        // if leave hours on the day is more than the standard hours, use the standard hour to calculate the adjustment
                        if (noAccrualHours.abs().compareTo(dayHours.abs()) == 1) {
                            noAccrualHours = dayHours.abs().negate();
                        }
                        BigDecimal noAccrualRate = dayRate.multiply(noAccrualHours.divide(dayHours));
                        this.calculateHours(anAC.getLmAccrualCategoryId(), ftePercentage, noAccrualRate,
                                accumulatedAccrualCatToNegativeAccrualAmounts);
                    }

                    // only accrual on work days
                    if (!TKUtils.isWeekend(currentDate) && !fullFteGranted) {
                        //Add to total accumulatedAccrualCatToAccrualAmounts
                        //use rule and ftePercentage to calculate the hours            
                        this.calculateHours(anAC.getLmAccrualCategoryId(), ftePercentage, dayRate,
                                accumulatedAccrualCatToAccrualAmounts);
                    }
                    //Determine if we are at the accrual earn interval in the span, if so add leave block for accumulated accrual amount to list
                    //and reset accumulatedAccrualCatToAccrualAmounts and accumulatedAccrualCatToNegativeAccrualAmounts for this accrual category
                    if (this.isDateAnIntervalDate(currentDate.toLocalDate(), anAC.getAccrualEarnInterval(),
                            phra.getPayCalendar(), rrAggregate.getCalEntryMap())) {
                        BigDecimal acHours = accumulatedAccrualCatToAccrualAmounts
                                .get(anAC.getLmAccrualCategoryId());

                        if (acHours != null) {
                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), acHours,
                                    anAC, null, true, currentRange.getLeaveCalendarDocumentId(), null);
                            accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId()); // reset accumulatedAccrualCatToAccrualAmounts
                            fullFteGranted = false;
                        }

                        BigDecimal adjustmentHours = accumulatedAccrualCatToNegativeAccrualAmounts
                                .get(anAC.getLmAccrualCategoryId());
                        if (adjustmentHours != null && adjustmentHours.compareTo(BigDecimal.ZERO) != 0) {
                            // do not create leave block if the ajustment amount is 0
                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(),
                                    adjustmentHours, anAC, null, false, currentRange.getLeaveCalendarDocumentId(),
                                    null);
                            accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId()); // reset accumulatedAccrualCatToNegativeAccrualAmounts
                        }
                    }
                }
            }
            //Determine if today is a system scheduled time off and accrue holiday if so.
            SystemScheduledTimeOffContract ssto = currentRange.getSysScheTimeOff();
            if (ssto != null) {
                AccrualCategory anAC = HrServiceLocator.getAccrualCategoryService()
                        .getAccrualCategory(ssto.getAccrualCategory(), ssto.getEffectiveLocalDate());
                if (anAC == null) {
                    LOG.error("Cannot find Accrual Category for system scheduled time off "
                            + ssto.getLmSystemScheduledTimeOffId());
                    return;
                    //               throw new RuntimeException("Cannot find Accrual Category for system scheduled time off " + ssto.getLmSystemScheduledTimeOffId());
                }
                BigDecimal hrs = ssto.getAmountofTime().multiply(ftePercentage);
                // system scheduled time off leave block
                createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), hrs, anAC,
                        ssto.getLmSystemScheduledTimeOffId(), true, currentRange.getLeaveCalendarDocumentId(),
                        null);
                // we only need to create usage leave block for ssto if there is a scheduled time off date
                if (ssto.getScheduledTimeOffDate() != null) {
                    // usage leave block with negative amount. Assign primary leave assignment information to ssto usage leave block
                    createLeaveBlock(principalId, accrualLeaveBlocks, ssto.getScheduledTimeOffLocalDate(),
                            hrs.negate(), anAC, ssto.getLmSystemScheduledTimeOffId(), true,
                            currentRange.getLeaveCalendarDocumentId(), currentRange.getPrimaryLeaveAssignmentId());
                }
            }
            // if today is the last day of the employment, create leave blocks if there's any hours available
            if (endPhra != null && currentDate.toLocalDate().equals(endPhra.getEffectiveLocalDate())) {
                // accumulated accrual amount
                if (!accumulatedAccrualCatToAccrualAmounts.isEmpty()) {
                    for (Map.Entry<String, BigDecimal> entry : accumulatedAccrualCatToAccrualAmounts.entrySet()) {
                        if (entry.getValue() != null && entry.getValue().compareTo(BigDecimal.ZERO) != 0) {
                            AccrualCategory anAC = HrServiceLocator.getAccrualCategoryService()
                                    .getAccrualCategory(entry.getKey());
                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(),
                                    entry.getValue(), anAC, null, true, currentRange.getLeaveCalendarDocumentId(),
                                    null);
                        }
                    }
                    accumulatedAccrualCatToAccrualAmounts = new HashMap<String, BigDecimal>(); // reset accumulatedAccrualCatToAccrualAmounts
                }
                // negative/adjustment accrual amount
                if (!accumulatedAccrualCatToNegativeAccrualAmounts.isEmpty()) {
                    for (Map.Entry<String, BigDecimal> entry : accumulatedAccrualCatToNegativeAccrualAmounts
                            .entrySet()) {
                        if (entry.getValue() != null && entry.getValue().compareTo(BigDecimal.ZERO) != 0) {
                            AccrualCategory anAC = HrServiceLocator.getAccrualCategoryService()
                                    .getAccrualCategory(entry.getKey());
                            createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(),
                                    entry.getValue(), anAC, null, true, currentRange.getLeaveCalendarDocumentId(),
                                    null);
                        }
                    }
                    accumulatedAccrualCatToNegativeAccrualAmounts = new HashMap<String, BigDecimal>(); // reset accumulatedAccrualCatToNegativeAccrualAmounts
                }
                phra = null; // reset principal attribute so new value will be retrieved
                endPhra = null; // reset end principal attribute so new value will be retrieved
            }

            currentDate = currentDate.plusDays(1);
        }

        //Save accrual leave blocks at the very end
        LmServiceLocator.getLeaveBlockService().saveLeaveBlocks(accrualLeaveBlocks);

        // record timestamp of this accrual run in database
        if (recordRanData) {
            LmServiceLocator.getPrincipalAccrualRanService().updatePrincipalAccrualRanInfo(principalId);
        }

    }

    private void deactivateOldAccruals(String principalId, DateTime startDate, DateTime endDate,
            String runAsPrincipalId) {
        List<LeaveBlock> previousLB = LmServiceLocator.getLeaveBlockService()
                .getAccrualGeneratedLeaveBlocks(principalId, startDate.toLocalDate(), endDate.toLocalDate());
        List<LeaveBlock> sstoAccrualList = new ArrayList<LeaveBlock>();
        List<LeaveBlock> sstoUsageList = new ArrayList<LeaveBlock>();

        for (LeaveBlock lb : previousLB) {
            if (StringUtils.isNotEmpty(lb.getScheduleTimeOffId())) {
                if (lb.getLeaveAmount().compareTo(BigDecimal.ZERO) > 0) {
                    sstoAccrualList.add(lb);
                } else if (lb.getLeaveAmount().compareTo(BigDecimal.ZERO) < 0) {
                    sstoUsageList.add(lb);
                }
            } else {
                if (!(StringUtils.equals(lb.getLeaveBlockType(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER)
                        || StringUtils.equals(lb.getLeaveBlockType(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT))) {
                    LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(),
                            runAsPrincipalId);
                }
            }
        }

        for (LeaveBlock accrualLb : sstoAccrualList) {
            for (LeaveBlock usageLb : sstoUsageList) {
                // both usage and accrual ssto leave blocks are there, so the ssto accural is not banked, removed both leave blocks
                // 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
                if (accrualLb.getScheduleTimeOffId().equals(usageLb.getScheduleTimeOffId())) {
                    LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(accrualLb.getLmLeaveBlockId(),
                            runAsPrincipalId);
                    LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(usageLb.getLmLeaveBlockId(),
                            runAsPrincipalId);
                }
            }
        }

    }

    private BigDecimal getNotEligibleForAccrualHours(String principalId, LocalDate currentDate) {
        BigDecimal hours = BigDecimal.ZERO;
        // check if there's any manual not-eligible-for-accrual leave blocks, use the hours of the leave block to adjust accrual calculation 
        List<LeaveBlock> lbs = LmServiceLocator.getLeaveBlockService()
                .getNotAccrualGeneratedLeaveBlocksForDate(principalId, currentDate);
        for (LeaveBlock lb : lbs) {
            EarnCodeContract ec = HrServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), currentDate);
            if (ec == null) {
                LOG.error("Cannot find Earn Code for Leave block " + lb.getLmLeaveBlockId());
                return null;
                //            throw new RuntimeException("Cannot find Earn Code for Leave block " + lb.getLmLeaveBlockId());
            }
            if (ec.getEligibleForAccrual().equals("N")
                    && ec.getAccrualBalanceAction().equals(HrConstants.ACCRUAL_BALANCE_ACTION.USAGE)
                    && lb.getLeaveAmount().compareTo(BigDecimal.ZERO) != 0) {
                hours = hours.add(lb.getLeaveAmount());
            }
        }
        return hours;
    }

    private void createLeaveBlock(String principalId, List<LeaveBlock> accrualLeaveBlocks, LocalDate leaveDate,
            BigDecimal hrs, AccrualCategoryContract anAC, String sysSchTimeOffId, boolean createZeroLeaveBlock,
            String leaveDocId, String primaryAssignmentId) {
        // Replacing Leave Code to earn code - KPME 1634
        EarnCodeContract ec = HrServiceLocator.getEarnCodeService().getEarnCode(anAC.getEarnCode(),
                anAC.getEffectiveLocalDate());
        if (ec == null) {
            //         throw new RuntimeException("Cannot find Earn Code for Accrual category " + anAC.getAccrualCategory());
            LOG.error("Cannot find Earn Code for Accrual category " + anAC.getAccrualCategory());
            return;
        }
        // use rounding option and fract time allowed of Leave Code to round the leave block hours
        BigDecimal roundedHours = HrServiceLocator.getEarnCodeService().roundHrsWithEarnCode(hrs, ec);
        if (!createZeroLeaveBlock && roundedHours.compareTo(BigDecimal.ZERO) == 0) {
            return; // do not create leave block with zero amount
        }
        LeaveBlockBo aLeaveBlock = new LeaveBlockBo();
        aLeaveBlock.setAccrualCategory(anAC.getAccrualCategory());
        aLeaveBlock.setLeaveLocalDate(leaveDate);
        aLeaveBlock.setPrincipalId(principalId);
        //More than one earn code can be associated with an accrual category. Which one does this get?
        aLeaveBlock.setEarnCode(anAC.getEarnCode());
        aLeaveBlock.setAccrualGenerated(true);
        aLeaveBlock.setBlockId(0L);
        aLeaveBlock.setScheduleTimeOffId(sysSchTimeOffId);
        aLeaveBlock.setLeaveAmount(roundedHours);
        aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
        aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.APPROVED);
        aLeaveBlock.setDocumentId(leaveDocId);

        if (StringUtils.isNotBlank(primaryAssignmentId)) {
            Assignment primAssignment = HrServiceLocator.getAssignmentService().getAssignment(primaryAssignmentId);
            if (primAssignment != null) {
                aLeaveBlock.setGroupKeyCode(primAssignment.getGroupKeyCode());
                aLeaveBlock.setWorkArea(primAssignment.getWorkArea());
                aLeaveBlock.setJobNumber(primAssignment.getJobNumber());
                aLeaveBlock.setTask(primAssignment.getTask());
            }
        }

        accrualLeaveBlocks.add(LeaveBlockBo.to(aLeaveBlock));

    }

    private void createEmptyLeaveBlockForStatusChange(String principalId, List<LeaveBlock> accrualLeaveBlocks,
            LocalDate leaveDate) {
        LeaveBlockBo aLeaveBlock = new LeaveBlockBo();
        aLeaveBlock.setAccrualCategory(null);
        aLeaveBlock.setLeaveLocalDate(leaveDate);
        aLeaveBlock.setPrincipalId(principalId);
        aLeaveBlock.setEarnCode(LMConstants.STATUS_CHANGE_EARN_CODE); // fake leave code
        aLeaveBlock.setAccrualGenerated(true);
        aLeaveBlock.setBlockId(0L);
        aLeaveBlock.setScheduleTimeOffId(null);
        aLeaveBlock.setLeaveAmount(BigDecimal.ZERO);
        aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
        aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.APPROVED);

        accrualLeaveBlocks.add(LeaveBlockBo.to(aLeaveBlock));

    }

    private void calculateHours(String accrualCategoryId, BigDecimal fte, BigDecimal rate,
            Map<String, BigDecimal> accumulatedAmounts) {
        BigDecimal hours = rate.multiply(fte);
        BigDecimal oldHours = accumulatedAmounts.get(accrualCategoryId);
        BigDecimal newHours = oldHours == null ? hours : hours.add(oldHours);
        accumulatedAmounts.put(accrualCategoryId, newHours);
    }

    public DateTime getStartAccrualDate(String principalId) {
        return null;
    }

    public DateTime getEndAccrualDate(String principalId) {
        return null;
    }

    @Override
    public void runAccrual(List<String> principalIds) {
        for (String principalId : principalIds) {
            runAccrual(principalId);
        }
    }

    private boolean isDateAnIntervalDate(LocalDate aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
            return isDateAtPayCalInterval(aDate, earnInterval, payCalName, aMap);
        } else {
            return this.isDateAtEarnInterval(aDate, earnInterval);
        }
    }

    private boolean isDateAtPayCalInterval(LocalDate aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (StringUtils.isNotEmpty(payCalName) && !aMap.isEmpty()
                && earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) { // only used for ac earn interval == pay calendar
            List<CalendarEntry> entryList = aMap.get(payCalName);
            if (CollectionUtils.isNotEmpty(entryList)) {
                for (CalendarEntry anEntry : entryList) {
                    // 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
                    LocalDate endDate = anEntry.getEndPeriodFullDateTime().toLocalDate().minusDays(1);
                    if (aDate.compareTo(endDate) == 0) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    @Override
    public boolean isDateAtEarnInterval(LocalDate aDate, String earnInterval) {
        boolean atEarnInterval = false;
        AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
        if (accrualEarnInterval != null) {
            if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
                atEarnInterval = true;
            } else if (AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
                // figure out if the day is a Saturday
                if (aDate.getDayOfWeek() == DateTimeConstants.SATURDAY) {
                    atEarnInterval = true;
                }
            } else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
                // either the 15th or the last day of the month
                if (aDate.getDayOfMonth() == 15 || aDate.getDayOfMonth() == aDate.dayOfMonth().getMaximumValue()) {
                    atEarnInterval = true;
                }
            } else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
                // the last day of the month
                if (aDate.getDayOfMonth() == aDate.dayOfMonth().getMaximumValue()) {
                    atEarnInterval = true;
                }
            } else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
                // the last day of the year
                if (aDate.getDayOfYear() == aDate.dayOfYear().getMaximumValue()) {
                    atEarnInterval = true;
                }
            } else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
                // no calculation
            }
        }
        return atEarnInterval;
    }

    @Override
    public RateRangeAggregate buildRateRangeAggregate(String principalId, DateTime startDate, DateTime endDate) {
        RateRangeAggregate rrAggregate = new RateRangeAggregate();
        List<RateRange> rateRangeList = new ArrayList<RateRange>();
        // get all active jobs that are effective before the endDate
        List<Job> activeJobs = HrServiceLocator.getJobService().getAllActiveLeaveJobs(principalId,
                endDate.toLocalDate());
        List<Job> inactiveJobs = HrServiceLocator.getJobService().getAllInActiveLeaveJobsInRange(principalId,
                endDate.toLocalDate());

        List<PrincipalHRAttributes> phaList = HrServiceLocator.getPrincipalHRAttributeService()
                .getAllActivePrincipalHrAttributesForPrincipalId(principalId, endDate.toLocalDate());
        List<PrincipalHRAttributes> inactivePhaList = HrServiceLocator.getPrincipalHRAttributeService()
                .getAllInActivePrincipalHrAttributesForPrincipalId(principalId, endDate.toLocalDate());

        if (activeJobs.isEmpty() || phaList.isEmpty()) {
            return rrAggregate;
        }

        Set<String> phaLpSet = new HashSet<String>();
        Set<String> calNameSet = new HashSet<String>();
        if (CollectionUtils.isNotEmpty(phaList)) {
            for (PrincipalHRAttributes pha : phaList) {
                phaLpSet.add(pha.getLeavePlan());
                calNameSet.add(pha.getPayCalendar());
            }
        }

        List<LeavePlan> activeLpList = new ArrayList<LeavePlan>();
        List<LeavePlan> inactiveLpList = new ArrayList<LeavePlan>();
        for (String lpString : phaLpSet) {
            List<LeavePlan> aList = HrServiceLocator.getLeavePlanService().getAllActiveLeavePlan(lpString,
                    endDate.toLocalDate());
            activeLpList.addAll(aList);

            aList = HrServiceLocator.getLeavePlanService().getAllInActiveLeavePlan(lpString, endDate.toLocalDate());
            inactiveLpList.addAll(aList);
        }

        // get all pay calendar entries for this employee. used to determine interval dates
        Map<String, List<CalendarEntry>> calEntryMap = new HashMap<String, List<CalendarEntry>>();
        for (String calName : calNameSet) {
            Calendar aCal = HrServiceLocator.getCalendarService().getCalendarByGroup(calName);
            if (aCal != null) {
                List<CalendarEntry> aList = HrServiceLocator.getCalendarEntryService()
                        .getAllCalendarEntriesForCalendarId(aCal.getHrCalendarId());
                Collections.sort(aList);
                calEntryMap.put(calName, aList);
            }
        }
        rrAggregate.setCalEntryMap(calEntryMap);

        Set<String> lpStringSet = new HashSet<String>();
        if (CollectionUtils.isNotEmpty(activeLpList)) {
            for (LeavePlan lp : activeLpList) {
                lpStringSet.add(lp.getLeavePlan());
            }
        }
        List<SystemScheduledTimeOffContract> sstoList = new ArrayList<SystemScheduledTimeOffContract>();
        for (String lpString : lpStringSet) {
            List<? extends SystemScheduledTimeOffContract> aList = LmServiceLocator.getSysSchTimeOffService()
                    .getSystemScheduledTimeOffsForLeavePlan(startDate.toLocalDate(), endDate.toLocalDate(),
                            lpString);
            if (CollectionUtils.isNotEmpty(aList)) {
                sstoList.addAll(aList);
            }
        }

        List<AccrualCategory> activeAccrCatList = new ArrayList<AccrualCategory>();
        List<AccrualCategory> inactiveAccrCatList = new ArrayList<AccrualCategory>();
        for (String lpString : lpStringSet) {
            List<AccrualCategory> aList = HrServiceLocator.getAccrualCategoryService()
                    .getActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate.toLocalDate());
            if (CollectionUtils.isNotEmpty(aList)) {
                activeAccrCatList.addAll(aList);
            }

            aList = HrServiceLocator.getAccrualCategoryService()
                    .getInActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate.toLocalDate());
            if (CollectionUtils.isNotEmpty(aList)) {
                inactiveAccrCatList.addAll(aList);
            }
        }

        List<AccrualCategoryRule> activeRuleList = new ArrayList<AccrualCategoryRule>();
        List<AccrualCategoryRule> inactiveRuleList = new ArrayList<AccrualCategoryRule>();
        for (AccrualCategory ac : activeAccrCatList) {
            List<AccrualCategoryRule> aRuleList = HrServiceLocator.getAccrualCategoryRuleService()
                    .getActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId());
            activeRuleList.addAll(aRuleList);

            aRuleList = HrServiceLocator.getAccrualCategoryRuleService()
                    .getInActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId());
            inactiveRuleList.addAll(aRuleList);
        }

        List<LeaveCalendarDocumentHeader> lcDocList = LmServiceLocator.getLeaveCalendarDocumentHeaderService()
                .getAllDocumentHeadersInRangeForPricipalId(principalId, startDate, endDate);

        BigDecimal previousFte = null;
        List<Job> jobs;

        DateTime currentDate = startDate;
        while (!currentDate.isAfter(endDate)) {
            RateRange rateRange = new RateRange();

            jobs = this.getJobsForDate(activeJobs, inactiveJobs, currentDate.toLocalDate());
            if (jobs.isEmpty()) { // no jobs found for this day
                currentDate = currentDate.plusDays(1);
                continue;
            }
            rateRange.setJobs(jobs);

            // detect if there's a status change
            BigDecimal fteSum = HrServiceLocator.getJobService().getFteSumForJobs(jobs);
            rateRange.setAccrualRatePercentageModifier(fteSum);
            BigDecimal standardHours = HrServiceLocator.getJobService().getStandardHoursSumForJobs(jobs);
            rateRange.setStandardHours(standardHours);

            if (previousFte != null && !previousFte.equals(fteSum)) {
                rateRange.setStatusChanged(true);
                rrAggregate.setRateRangeChanged(true);
            }
            previousFte = fteSum;

            // figure out the PrincipalHRAttributes for this day
            PrincipalHRAttributes phra = this.getPrincipalHrAttributesForDate(phaList, currentDate.toLocalDate());
            rateRange.setPrincipalHRAttributes(phra);

            if (rateRange.getPrincipalHRAttributes() != null) {
                // figure out if there's an end principalHrAttributes for the initial principalHRAttributes
                PrincipalHRAttributes endPhra = this.getInactivePrincipalHrAttributesForDate(inactivePhaList,
                        rateRange.getPrincipalHRAttributes().getEffectiveLocalDate(), currentDate.toLocalDate());
                rateRange.setEndPrincipalHRAttributes(endPhra);
            }

            // get leave plan for this day
            if (rateRange.getPrincipalHRAttributes() != null) {
                rateRange.setLeavePlan(this.getLeavePlanForDate(activeLpList, inactiveLpList,
                        rateRange.getPrincipalHRAttributes().getLeavePlan(), currentDate.toLocalDate()));
            }

            if (rateRange.getLeavePlan() != null) {
                // get accrual category list for this day
                List<AccrualCategory> acsForDay = this.getAccrualCategoriesForDate(activeAccrCatList,
                        inactiveAccrCatList, rateRange.getLeavePlan().getLeavePlan(), currentDate.toLocalDate());
                rateRange.setAcList(acsForDay);

                // get System scheduled time off for this day
                for (SystemScheduledTimeOffContract ssto : sstoList) {
                    if (ssto.getAccruedLocalDate().equals(currentDate.toLocalDate())
                            && ssto.getLeavePlan().equals(rateRange.getLeavePlan().getLeavePlan())) {

                        // figure out the primary leave assignment to use for ssto usage leave blocks
                        if (CollectionUtils.isNotEmpty(jobs)
                                && StringUtils.isBlank(rateRange.getPrimaryLeaveAssignmentId())) {
                            for (Job aJob : jobs) {
                                if (aJob.isEligibleForLeave() && aJob.isPrimaryJob()) {
                                    List<Assignment> assignmentList = HrServiceLocator.getAssignmentService()
                                            .getActiveAssignmentsForJob(principalId, aJob.getJobNumber(),
                                                    currentDate.toLocalDate());
                                    for (Assignment anAssignment : assignmentList) {
                                        if (anAssignment != null && anAssignment.isPrimaryAssign()) {
                                            rateRange.setPrimaryLeaveAssignmentId(anAssignment.getTkAssignmentId());
                                            break;
                                        }
                                    }
                                }
                            }
                        }

                        // 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
                        // this logic depends on the deactivateOldAccruals() runs before buildRateRangeAggregate()
                        // because deactivateOldAccruals() removes accrued ssto leave blocks unless they are banked/transferred
                        List<LeaveBlock> sstoLbList = LmServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(
                                principalId, ssto.getLmSystemScheduledTimeOffId(), ssto.getAccruedLocalDate());
                        if (CollectionUtils.isEmpty(sstoLbList)) {
                            rateRange.setSysScheTimeOff(ssto);
                        }
                    }
                }
            }
            // set accrual category rules for the day
            if (CollectionUtils.isNotEmpty(rateRange.getAcList())) {
                List<AccrualCategoryRule> rulesForDay = new ArrayList<AccrualCategoryRule>();
                for (AccrualCategory ac : rateRange.getAcList()) {
                    rulesForDay.addAll(this.getAccrualCategoryRulesForDate(activeRuleList,
                            ac.getLmAccrualCategoryId(), currentDate.toLocalDate(),
                            rateRange.getPrincipalHRAttributes().getServiceLocalDate()));
                }
                rateRange.setAcRuleList(rulesForDay);

            }

            Interval range = new Interval(currentDate, currentDate.plusDays(1));
            rateRange.setRange(range);
            // assign leave document id to range if there is an existing leave doc for currentDate.
            // The doc Id will be assigned to leave blocks created at this rate range
            rateRange.setLeaveCalendarDocumentId(this.getLeaveDocumentForDate(lcDocList, currentDate));
            rateRangeList.add(rateRange);

            currentDate = currentDate.plusDays(1);
        }
        rrAggregate.setRateRanges(rateRangeList);
        rrAggregate.setCurrentRate(null);
        return rrAggregate;
    }

    private String getLeaveDocumentForDate(List<LeaveCalendarDocumentHeader> lcDocList, DateTime currentDate) {
        for (LeaveCalendarDocumentHeader lcdh : lcDocList) {
            if (!lcdh.getBeginDateTime().isAfter(currentDate) && lcdh.getEndDateTime().isAfter(currentDate)) {
                return lcdh.getDocumentId();
            }
        }
        return "";
    }

    public List<Job> getJobsForDate(List<Job> activeJobs, List<Job> inactiveJobs, LocalDate currentDate) {
        List<Job> jobs = new ArrayList<Job>();
        for (Job aJob : activeJobs) {
            if (!aJob.getEffectiveLocalDate().isAfter(currentDate)) {
                jobs.add(aJob);
            }
        }
        if (CollectionUtils.isNotEmpty(jobs)) {
            List<Job> tempList = new ArrayList<Job>();
            tempList.addAll(jobs);
            for (Job aJob : tempList) {
                for (Job inactiveJob : inactiveJobs) {
                    if (inactiveJob.getJobNumber().equals(aJob.getJobNumber())
                            && inactiveJob.getEffectiveLocalDate().isAfter(aJob.getEffectiveLocalDate())
                            && !inactiveJob.getEffectiveLocalDate().isAfter(currentDate)) {
                        // remove inactive job from list
                        jobs.remove(aJob);
                    }
                }
            }
        }
        return jobs;
    }

    public PrincipalHRAttributes getPrincipalHrAttributesForDate(List<PrincipalHRAttributes> activeList,
            LocalDate currentDate) {
        List<PrincipalHRAttributes> phasForDay = new ArrayList<PrincipalHRAttributes>();
        for (PrincipalHRAttributes pha : activeList) {
            if (pha != null && pha.getEffectiveLocalDate() != null && pha.getServiceLocalDate() != null
                    && !pha.getEffectiveLocalDate().isAfter(currentDate)
                    && !pha.getServiceLocalDate().isAfter(currentDate)) {
                phasForDay.add(pha);
            }
        }
        if (CollectionUtils.isNotEmpty(phasForDay)) {
            PrincipalHRAttributes pha = phasForDay.get(0);
            int indexOfMaxEffDt = 0;
            if (phasForDay.size() > 1) {
                for (int i = 1; i < phasForDay.size(); i++) {
                    if ((phasForDay.get(i).getEffectiveLocalDate()
                            .isAfter(phasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()))
                            || (phasForDay.get(i).getEffectiveLocalDate()
                                    .equals(phasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate())
                                    && phasForDay.get(i).getCreateTime()
                                            .isAfter(phasForDay.get(indexOfMaxEffDt).getCreateTime()))) {
                        indexOfMaxEffDt = i;
                    }
                }
                pha = phasForDay.get(indexOfMaxEffDt);
            }
            return pha;
        }
        return null;
    }

    public PrincipalHRAttributes getInactivePrincipalHrAttributesForDate(List<PrincipalHRAttributes> inactiveList,
            LocalDate activeDate, LocalDate currentDate) {
        List<PrincipalHRAttributes> inactivePhasForDay = new ArrayList<PrincipalHRAttributes>();
        for (PrincipalHRAttributes pha : inactiveList) {
            if (pha.getEffectiveLocalDate().isAfter(activeDate)
                    && !pha.getServiceLocalDate().isAfter(currentDate)) {
                inactivePhasForDay.add(pha);
            }
        }
        if (CollectionUtils.isNotEmpty(inactivePhasForDay)) {
            PrincipalHRAttributes pha = inactivePhasForDay.get(0);
            int indexOfMaxEffDt = 0;
            if (inactivePhasForDay.size() > 1) {
                for (int i = 1; i < inactivePhasForDay.size(); i++) {
                    if ((inactivePhasForDay.get(i).getEffectiveLocalDate()
                            .isAfter(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()))
                            || (inactivePhasForDay.get(i).getEffectiveLocalDate()
                                    .equals(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate())
                                    && inactivePhasForDay.get(i).getCreateTime()
                                            .isAfter(inactivePhasForDay.get(indexOfMaxEffDt).getCreateTime()))) {
                        indexOfMaxEffDt = i;
                    }
                }
                pha = inactivePhasForDay.get(indexOfMaxEffDt);
            }
            return pha;
        }
        return null;
    }

    public LeavePlan getLeavePlanForDate(List<LeavePlan> activeLpList, List<LeavePlan> inactiveLpList,
            String leavePlan, LocalDate currentDate) {
        List<LeavePlan> lpsForDay = new ArrayList<LeavePlan>();
        for (LeavePlan lp : activeLpList) {
            if (lp.getLeavePlan().equals(leavePlan) && !lp.getEffectiveLocalDate().isAfter(currentDate)) {
                lpsForDay.add(lp);
            }
        }
        List<LeavePlan> aList = new ArrayList<LeavePlan>();
        aList.addAll(lpsForDay);
        for (LeavePlan lp : aList) {
            for (LeavePlan inactiveLp : inactiveLpList) {
                if (inactiveLp.getLeavePlan().equals(lp.getLeavePlan())
                        && inactiveLp.getEffectiveLocalDate().isAfter(lp.getEffectiveLocalDate())
                        && !inactiveLp.getEffectiveLocalDate().isAfter(currentDate)) {
                    // remove inactive leave plan from list
                    lpsForDay.remove(lp);
                }
            }
        }
        if (CollectionUtils.isNotEmpty(lpsForDay)) {
            LeavePlan aLp = lpsForDay.get(0);
            int indexOfMaxEffDt = 0;
            if (lpsForDay.size() > 1) {
                for (int i = 1; i < lpsForDay.size(); i++) {
                    if ((lpsForDay.get(i).getEffectiveLocalDate()
                            .isAfter(lpsForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()))
                            || (lpsForDay.get(i).getEffectiveLocalDate()
                                    .compareTo(lpsForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()) == 0
                                    && lpsForDay.get(i).getCreateTime()
                                            .isAfter(lpsForDay.get(indexOfMaxEffDt).getCreateTime()))) {
                        indexOfMaxEffDt = i;
                    }
                }
                aLp = lpsForDay.get(indexOfMaxEffDt);
            }
            return aLp;
        }
        return null;
    }

    public List<AccrualCategory> getAccrualCategoriesForDate(List<AccrualCategory> activeAccrCatList,
            List<AccrualCategory> inactiveAccrCatList, String leavePlan, LocalDate currentDate) {
        Set<AccrualCategory> aSet = new HashSet<AccrualCategory>();
        for (AccrualCategory ac : activeAccrCatList) {
            if (ac.getLeavePlan().equals(leavePlan) && !ac.getEffectiveLocalDate().isAfter(currentDate)) {
                aSet.add(ac);
            }
        }
        List<AccrualCategory> list1 = new ArrayList<AccrualCategory>();
        list1.addAll(aSet);
        for (AccrualCategory ac : list1) {
            for (AccrualCategory inactiveAc : inactiveAccrCatList) {
                if (inactiveAc.getAccrualCategory().equals(ac.getAccrualCategory())
                        && inactiveAc.getEffectiveLocalDate().isAfter(ac.getEffectiveLocalDate())
                        && !inactiveAc.getEffectiveLocalDate().isAfter(currentDate)) {
                    // remove inactive accrual category from list
                    aSet.remove(ac);
                }
            }
        }
        List<AccrualCategory> acsForDay = new ArrayList<AccrualCategory>();
        acsForDay.addAll(aSet);
        return acsForDay;
    }

    @Override
    public boolean isEmpoyeementFutureStatusChanged(String principalId, DateTime startDate, DateTime endDate) {
        if (endDate.isAfter(LocalDate.now().toDateTimeAtStartOfDay())) {
            RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate);
            if (rrAggregate.isRateRangeChanged()) {
                return true;
            }
        }
        return false;
    }

    @Override
    public void calculateFutureAccrualUsingPlanningMonth(String principalId, LocalDate asOfDate,
            String runAsPrincipalId) {
        PrincipalHRAttributes phra = HrServiceLocator.getPrincipalHRAttributeService()
                .getPrincipalCalendar(principalId, asOfDate);
        if (phra != null) {
            // use the date from pay period to get the leave plan
            LeavePlanContract lp = HrServiceLocator.getLeavePlanService().getLeavePlan(phra.getLeavePlan(),
                    asOfDate);
            if (lp != null && StringUtils.isNotEmpty(lp.getPlanningMonths())) {
                // go back a year 
                LocalDate startDate = asOfDate.minusYears(1);
                if (startDate.getDayOfMonth() > startDate.dayOfMonth().getMaximumValue()) {
                    startDate = startDate.withDayOfMonth(startDate.dayOfMonth().getMaximumValue());
                }
                // go forward using planning months
                LocalDate endDate = asOfDate.plusMonths(Integer.parseInt(lp.getPlanningMonths()));
                // max days in months differ, if the date is bigger than the max day, set it to the max day of the month
                if (endDate.getDayOfMonth() > endDate.dayOfMonth().getMaximumValue()) {
                    endDate = endDate.withDayOfMonth(endDate.dayOfMonth().getMaximumValue());
                }
                runAccrual(principalId, startDate.toDateTimeAtStartOfDay(), endDate.toDateTimeAtStartOfDay(), true,
                        runAsPrincipalId);

            }
        }
    }

    private boolean minimumPercentageReachedForPayPeriod(BigDecimal min, String earnInterval, int workDays,
            DateTime intervalDate, String payCalName, Map<String, List<CalendarEntry>> aMap) {
        if (min == null || min.compareTo(BigDecimal.ZERO) == 0) {
            return true;
        }
        int daysInInterval = this.getWorkDaysInInterval(intervalDate, earnInterval, payCalName, aMap);
        if (daysInInterval == 0) {
            return true;
        }
        BigDecimal actualPercentage = new BigDecimal(workDays).divide(new BigDecimal(daysInInterval), 2,
                BigDecimal.ROUND_HALF_EVEN);
        if (actualPercentage.compareTo(min) >= 0) {
            return true;
        }

        return false;
    }

    private DateTime getPrevIntervalDate(DateTime aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
            return this.getPrevPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
        } else {
            return this.getPreviousAccrualIntervalDate(earnInterval, aDate);
        }
    }

    @Override
    public DateTime getPreviousAccrualIntervalDate(String earnInterval, DateTime aDate) {
        DateTime previousAccrualIntervalDate = null;
        AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
        if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
            previousAccrualIntervalDate = aDate.minusDays(1);
        } else if (AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
            previousAccrualIntervalDate = aDate.minusWeeks(1).withDayOfWeek(DateTimeConstants.SATURDAY);
        } else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
            previousAccrualIntervalDate = aDate.minusDays(15);
            if (previousAccrualIntervalDate.getDayOfMonth() <= 15) {
                previousAccrualIntervalDate = previousAccrualIntervalDate.withDayOfMonth(15);
            } else {
                previousAccrualIntervalDate = previousAccrualIntervalDate
                        .withDayOfMonth(previousAccrualIntervalDate.dayOfMonth().getMaximumValue());
            }
        } else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
            previousAccrualIntervalDate = aDate.minusMonths(1);
            previousAccrualIntervalDate = previousAccrualIntervalDate
                    .withDayOfMonth(previousAccrualIntervalDate.dayOfMonth().getMaximumValue());
        } else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
            previousAccrualIntervalDate = aDate.minusYears(1);
            previousAccrualIntervalDate = previousAccrualIntervalDate
                    .withDayOfYear(previousAccrualIntervalDate.dayOfYear().getMaximumValue());
        } else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
            previousAccrualIntervalDate = aDate;
        }

        return previousAccrualIntervalDate;
    }

    private DateTime getPrevPayCalIntervalDate(DateTime aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (StringUtils.isNotEmpty(payCalName) && !aMap.isEmpty()
                && earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) { // only used for ac earn interval == pay calendar
            List<CalendarEntry> entryList = aMap.get(payCalName);
            if (CollectionUtils.isNotEmpty(entryList)) {
                for (CalendarEntry anEntry : entryList) {
                    // 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
                    DateTime endDate = anEntry.getEndPeriodFullDateTime().minusDays(1);
                    if (anEntry.getBeginPeriodFullDateTime().compareTo(aDate) <= 0
                            && endDate.compareTo(aDate) >= 0) {
                        // the day before the beginning date of the cal entry that contains the passed in date is the endDate of previous calendar entry
                        DateTime prevIntvDate = anEntry.getBeginPeriodFullDateTime().minusDays(1);
                        return prevIntvDate;
                    }
                }
            }
        }
        return aDate;
    }

    @Override
    public DateTime getNextIntervalDate(DateTime aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
            return this.getNextPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
        } else {
            return this.getNextAccrualIntervalDate(earnInterval, aDate);
        }
    }

    private DateTime getNextPayCalIntervalDate(DateTime aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (StringUtils.isNotEmpty(payCalName) && !aMap.isEmpty()
                && earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) { // only used for ac earn interval == pay calendar
            List<CalendarEntry> entryList = aMap.get(payCalName);
            if (CollectionUtils.isNotEmpty(entryList)) {
                for (CalendarEntry anEntry : entryList) {
                    // 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
                    DateTime endDate = anEntry.getEndPeriodFullDateTime().minusDays(1);
                    if (anEntry.getBeginPeriodFullDateTime().compareTo(aDate) <= 0
                            && endDate.compareTo(aDate) >= 0) {
                        // the endDate of the cal entry that contains the passed in date is the next pay calendar interval date
                        return endDate;
                    }
                }
            }
        }
        return aDate;
    }

    @Override
    public DateTime getNextAccrualIntervalDate(String earnInterval, DateTime aDate) {
        DateTime nextAccrualIntervalDate = null;
        AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
        if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
            nextAccrualIntervalDate = aDate;
        } else if (AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
            if (aDate.getDayOfWeek() != DateTimeConstants.SATURDAY) {
                nextAccrualIntervalDate = aDate.withDayOfWeek(DateTimeConstants.SATURDAY);
            } else {
                nextAccrualIntervalDate = aDate.withWeekOfWeekyear(1);
            }
        } else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
            if (aDate.getDayOfMonth() <= 15) {
                nextAccrualIntervalDate = aDate.withDayOfMonth(15);
            } else {
                nextAccrualIntervalDate = aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue());
            }
        } else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
            nextAccrualIntervalDate = aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue());
        } else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
            nextAccrualIntervalDate = aDate.withDayOfYear(aDate.dayOfYear().getMaximumValue());
        } else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
            nextAccrualIntervalDate = aDate;
        } else if (AccrualEarnInterval.PAY_CAL.equals(accrualEarnInterval)) {
            LOG.error(
                    "Accrual Earn Interval of Pay CAL is not valid for AccrualServiceImpl:getNextAccrualIntervalDate");
            nextAccrualIntervalDate = null;
        }

        return nextAccrualIntervalDate;
    }

    private int getWorkDaysInInterval(DateTime aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
            return this.getWorkDaysInPayCalInterval(aDate, earnInterval, payCalName, aMap);
        } else {
            return this.getWorkDaysInAccrualInterval(earnInterval, aDate);
        }
    }

    private int getWorkDaysInPayCalInterval(DateTime aDate, String earnInterval, String payCalName,
            Map<String, List<CalendarEntry>> aMap) {
        if (StringUtils.isNotEmpty(payCalName) && !aMap.isEmpty()
                && earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) { // only used for ac earn interval == pay calendar
            List<CalendarEntry> entryList = aMap.get(payCalName);
            if (CollectionUtils.isNotEmpty(entryList)) {
                for (CalendarEntry anEntry : entryList) {
                    // 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
                    DateTime endDate = anEntry.getEndPeriodFullDateTime().minusDays(1);
                    if (anEntry.getBeginPeriodFullDateTime().compareTo(aDate) <= 0
                            && endDate.compareTo(aDate) >= 0) {
                        return TKUtils.getWorkDays(anEntry.getBeginPeriodFullDateTime(), endDate);
                    }
                }
            }
        }
        return 0;
    }

    @Override
    public int getWorkDaysInAccrualInterval(String earnInterval, DateTime aDate) {
        AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
        if (accrualEarnInterval != null) {
            if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
                return 1;
            } else if (AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
                return 5;
            } else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
                if (aDate.getDayOfMonth() <= 15) {
                    return TKUtils.getWorkDays(aDate.withDayOfMonth(1), aDate.withDayOfMonth(15));
                } else {
                    return TKUtils.getWorkDays(aDate.withDayOfMonth(16),
                            aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue()));
                }
            } else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
                return TKUtils.getWorkDays(aDate.withDayOfMonth(1),
                        aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue()));
            } else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
                return TKUtils.getWorkDays(aDate.withDayOfYear(1),
                        aDate.withDayOfYear(aDate.dayOfYear().getMaximumValue()));
            } else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
                return 0;
            }
        }
        return 0;
    }

    public DateTime getRuleStartDate(String earnInterval, LocalDate serviceDate, Long startAcc) {
        DateTime ruleStartDate = null;

        String intervalValue = HrConstants.SERVICE_UNIT_OF_TIME.get(earnInterval);

        switch (intervalValue) {
        case "Months":
            ruleStartDate = serviceDate.toDateTimeAtStartOfDay().plusMonths(startAcc.intValue());
            if (ruleStartDate.getDayOfMonth() > ruleStartDate.dayOfMonth().getMaximumValue()) {
                ruleStartDate = ruleStartDate.withDayOfMonth(ruleStartDate.dayOfMonth().getMaximumValue());
            }
            break;
        case "Years":
            ruleStartDate = serviceDate.toDateTimeAtStartOfDay().withYear(startAcc.intValue());
            break;
        default:
            ruleStartDate = serviceDate.toDateTimeAtStartOfDay();
            break;
        }

        return ruleStartDate;
    }

    public boolean isProrationFlag(String proration) {
        if (proration == null) {
            return true;
        }
        return proration.equals("Y") ? true : false;
    }

    @Override
    public boolean statusChangedSinceLastRun(String principalId) {
        PrincipalAccrualRanContract par = LmServiceLocator.getPrincipalAccrualRanService()
                .getLastPrincipalAccrualRan(principalId);
        if (par == null) {
            return true;
        }
        JobContract aJob = HrServiceLocator.getJobService().getMaxTimestampJob(principalId);

        if (aJob != null && aJob.getCreateTime().isAfter(new DateTime(par.getLastRanTs().getTime()))) {
            return true;
        }

        Assignment anAssign = HrServiceLocator.getAssignmentService().getMaxTimestampAssignment(principalId);
        if (anAssign != null && anAssign.getCreateTime().isAfter(new DateTime(par.getLastRanTs().getTime()))) {
            return true;
        }

        PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService()
                .getMaxTimeStampPrincipalHRAttributes(principalId);
        if (pha != null && pha.getCreateTime().isAfter(par.getLastRanDateTime())) {
            return true;
        }
        // if there are leave blocks created for earn codes with eligible-for-accrual = no since the last accrual run, it should trigger recalculation 
        List<LeaveBlock> lbList = LmServiceLocator.getLeaveBlockService().getABELeaveBlocksSinceTime(principalId,
                par.getLastRanDateTime());
        return CollectionUtils.isNotEmpty(lbList);
    }

    public List<AccrualCategoryRule> getAccrualCategoryRulesForDate(List<AccrualCategoryRule> acrList,
            String accrualCategoryId, LocalDate currentDate, LocalDate serviceDate) {
        List<AccrualCategoryRule> aList = new ArrayList<AccrualCategoryRule>();
        if (CollectionUtils.isNotEmpty(acrList)) {
            for (AccrualCategoryRule acr : acrList) {
                if (acr.getLmAccrualCategoryId().equals(accrualCategoryId)) {
                    String uot = acr.getServiceUnitOfTime();
                    int startTime = acr.getStart().intValue();
                    int endTime = acr.getEnd().intValue();

                    LocalDate startDate = serviceDate;
                    LocalDate endDate = serviceDate;
                    if (uot.equals("M")) { // monthly
                        startDate = startDate.plusMonths(startTime);
                        endDate = endDate.plusMonths(endTime).minusDays(1);
                    } else if (uot.endsWith("Y")) { // yearly
                        startDate = startDate.plusYears(startTime);
                        endDate = endDate.plusYears(endTime).minusDays(1);
                    }

                    // max days in months differ, if the date is bigger than the max day, set it to the max day of the month
                    if (startDate.getDayOfMonth() > startDate.dayOfMonth().getMaximumValue()) {
                        startDate = startDate.withDayOfMonth(startDate.dayOfMonth().getMaximumValue());
                    }
                    if (endDate.getDayOfMonth() > endDate.dayOfMonth().getMaximumValue()) {
                        endDate = endDate.withDayOfMonth(endDate.dayOfMonth().getMaximumValue());
                    }

                    if (currentDate.compareTo(startDate) >= 0 && currentDate.compareTo(endDate) <= 0) {
                        aList.add(acr);
                    }
                }
            }
        }
        return aList;
    }

    public AccrualCategoryRuleContract getRuleForAccrualCategory(List<AccrualCategoryRule> acrList,
            AccrualCategoryContract ac) {
        if (CollectionUtils.isNotEmpty(acrList)) {
            for (AccrualCategoryRuleContract acr : acrList) {
                if (acr.getLmAccrualCategoryId().equals(ac.getLmAccrualCategoryId())) {
                    return acr;
                }
            }
        }
        return null;
    }

    @Override
    public BigDecimal getAccruedBalanceForPrincipal(String principalId, AccrualCategoryContract accrualCategory,
            LocalDate asOfDate) {
        BigDecimal balance = new BigDecimal(0);
        PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService()
                .getPrincipalCalendar(principalId, asOfDate);
        if (pha == null)
            return BigDecimal.ZERO;

        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithAccrualCategory(
                principalId, pha.getServiceLocalDate(), asOfDate, accrualCategory.getAccrualCategory());
        for (LeaveBlock block : leaveBlocks) {
            if (!(StringUtils.equals(block.getRequestStatus(), HrConstants.REQUEST_STATUS.DEFERRED)
                    || StringUtils.equals(block.getRequestStatus(), HrConstants.REQUEST_STATUS.DISAPPROVED))) {
                balance = balance.add(block.getLeaveAmount());
                /*             EarnCode code = accrualCategory.getEarnCodeObj();
                             if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Usage"))
                                balance = balance.subtract(block.getLeaveAmount().abs());
                             if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Adjustment"))
                                balance = balance.add(block.getLeaveAmount());*/
            }
        }
        return balance;
    }

    @Override
    public BigDecimal getApprovedBalanceForPrincipal(String principalId, AccrualCategoryContract accrualCategory,
            LocalDate asOfDate) {
        BigDecimal balance = new BigDecimal(0);
        PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService()
                .getPrincipalCalendar(principalId, asOfDate);
        List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithAccrualCategory(
                principalId, pha.getServiceLocalDate(), asOfDate, accrualCategory.getAccrualCategory());
        for (LeaveBlock block : leaveBlocks) {
            if (StringUtils.equals(block.getRequestStatus(), HrConstants.REQUEST_STATUS.APPROVED)) {
                balance = balance.add(block.getLeaveAmount());
                /*             EarnCode code = accrualCategory.getEarnCodeObj();
                             if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Usage"))
                                balance = balance.subtract(block.getLeaveAmount().abs());
                             if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Adjustment"))
                                balance = balance.add(block.getLeaveAmount());*/
            }
        }
        return balance;
    }

    @Override
    public void runAccrualForLeavePlan(LeavePlanContract aLeavePlan, DateTime startDate, DateTime endDate,
            boolean recordRanData) {
        if (aLeavePlan != null) {
            List<PrincipalHRAttributes> phaList = HrServiceLocator.getPrincipalHRAttributeService()
                    .getActiveEmployeesForLeavePlan(aLeavePlan.getLeavePlan(), aLeavePlan.getEffectiveLocalDate());
            for (PrincipalHRAttributes aPHA : phaList) {
                String anId = aPHA.getPrincipalId();
                if (LmServiceLocator.getLeaveAccrualService().statusChangedSinceLastRun(anId)) {
                    DateTime startDT = startDate == null ? getStartAccrualDate(anId) : startDate;
                    DateTime endDT = endDate == null ? getEndAccrualDate(anId) : endDate;
                    if (startDT != null && endDT != null) {
                        this.runAccrual(anId, startDT, endDT, recordRanData);
                    }
                }
            }
        }
    }
}