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

Java tutorial

Introduction

Here is the source code for org.kuali.kpme.tklm.leave.accrual.service.AccrualCategoryMaxBalanceServiceImpl.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.lang.StringUtils;
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.rule.AccrualCategoryRule;
import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRuleContract;
import org.kuali.kpme.core.api.calendar.Calendar;
import org.kuali.kpme.core.api.calendar.entry.CalendarEntry;
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.tklm.api.leave.accrual.AccrualCategoryMaxBalanceService;
import org.kuali.kpme.tklm.api.leave.block.LeaveBlock;
import org.kuali.kpme.tklm.api.leave.override.EmployeeOverrideContract;
import org.kuali.kpme.tklm.leave.block.LeaveBlockBo;
import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
import org.kuali.rice.krad.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.Map.Entry;

public class AccrualCategoryMaxBalanceServiceImpl implements AccrualCategoryMaxBalanceService {

    @Override
    public Map<String, Set<LeaveBlock>> getMaxBalanceViolations(CalendarEntry entry, String principalId) {

        Map<String, Set<LeaveBlock>> maxBalanceViolations = new HashMap<String, Set<LeaveBlock>>();

        Map<String, Set<LeaveBlock>> eligibilities = new HashMap<String, Set<LeaveBlock>>();

        eligibilities.put(HrConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE, new HashSet<LeaveBlock>());
        eligibilities.put(HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END, new HashSet<LeaveBlock>());
        eligibilities.put(HrConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND, new HashSet<LeaveBlock>());

        Interval thisEntryInterval = new Interval(entry.getBeginPeriodFullDateTime(),
                entry.getEndPeriodFullDateTime());

        LocalDate asOfDate = LocalDate.now();

        if (!thisEntryInterval.contains(asOfDate.toDate().getTime())) {
            asOfDate = entry.getEndPeriodFullDateTime().minusDays(1).toLocalDate();
        }
        PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService()
                .getPrincipalCalendar(principalId, asOfDate);

        if (pha == null) {
            return eligibilities;
        }
        Calendar cal = pha.getLeaveCalObj();

        if (cal == null) {
            return eligibilities;
        }
        //Consider time sheet intervals that stagger a leave period end date...
        List<CalendarEntry> leaveCalEntries = HrServiceLocator.getCalendarEntryService()
                .getCalendarEntriesEndingBetweenBeginAndEndDate(cal.getHrCalendarId(),
                        entry.getBeginPeriodFullDateTime(), entry.getEndPeriodFullDateTime());
        CalendarEntry yearEndLeaveEntry = null;
        CalendarEntry leaveLeaveEntry = null;
        if (!leaveCalEntries.isEmpty()) {
            for (CalendarEntry leaveEntry : leaveCalEntries) {
                if (StringUtils.equals(cal.getCalendarName(), leaveEntry.getCalendarName())) {
                    if (leaveEntry.getEndPeriodFullDateTime().compareTo(entry.getBeginPeriodFullDateTime()) > 0) {
                        leaveLeaveEntry = leaveEntry;
                    }
                }
            }
        }

        Interval leavePeriodInterval = null;
        Interval yearEndPeriodInterval = null;
        if (leaveLeaveEntry != null) {
            leavePeriodInterval = new Interval(entry.getBeginPeriodFullDateTime(),
                    leaveLeaveEntry.getEndPeriodFullDateTime());
            if (HrServiceLocator.getLeavePlanService().isLastCalendarPeriodOfLeavePlan(leaveLeaveEntry,
                    pha.getLeavePlan(), asOfDate)) {
                yearEndPeriodInterval = leavePeriodInterval;
            }
        }

        List<AccrualCategory> accrualCategories = HrServiceLocator.getAccrualCategoryService()
                .getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), asOfDate);

        if (!accrualCategories.isEmpty()) {

            List<LeaveBlock> leaveBlocks = new ArrayList<LeaveBlock>();
            Map<String, BigDecimal> accruedBalance = new HashMap<String, BigDecimal>();

            for (AccrualCategoryContract accrualCategory : accrualCategories) {
                LocalDate leaveBlockEndDate = leaveLeaveEntry == null
                        ? entry.getEndPeriodFullDateTime().toLocalDate().plusDays(1)
                        : leaveLeaveEntry.getEndPeriodFullDateTime().toLocalDate().plusDays(1);
                //if ()
                leaveBlocks.addAll(LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithAccrualCategory(
                        principalId, pha.getServiceLocalDate(), leaveBlockEndDate,
                        accrualCategory.getAccrualCategory()));
                accruedBalance.put(accrualCategory.getLmAccrualCategoryId(), BigDecimal.ZERO);
                /*   Un-comment to consider service interval end-point changes. i.e. when defining a new action frequency - "ON_SERVICE_MILESTONE"
                            List<AccrualCategoryRule> accrualRules = HrServiceLocator.getAccrualCategoryRuleService().getActiveAccrualCategoryRules(accrualCategory.getLmAccrualCategoryId());
                            for(AccrualCategoryRule rule : accrualRules) {
                               String serviceUnits = rule.getServiceUnitOfTime();
                               LocalDate rollOverDate = null;
                               //TODO: Accrual Category Rules' start and end field allow only whole integer values. This should be reflected in storage.
                               if(StringUtils.equals(serviceUnits, "M")) {
                                  rollOverDate = pha.getServiceLocalDate().plusMonths(rule.getEnd().intValue());
                               }
                               else if(StringUtils.equals(serviceUnits, "Y")) {
                                  rollOverDate = pha.getServiceLocalDate().plusYears(rule.getEnd().intValue());
                               }
                               if(ObjectUtils.isNotNull(rollOverDate)) {
                                  if(thisEntryInterval.contains(rollOverDate.minusDays(1).toDate().getTime())) {
                 //Add a max balance allocation leave block.
                 LeaveBlock allocation = new LeaveBlock();
                 allocation.setAccrualCategory(accrualCategory.getAccrualCategory());
                 allocation.setLeaveLocalDate(rollOverDate.minusDays(1));
                 allocation.setLeaveAmount(BigDecimal.ZERO);
                 allocation.setPrincipalId(principalId);
                 leaveBlocks.add(allocation);
                                  }
                               }
                            }
                */

                //Add a max balance allocation leave block.
                LeaveBlockBo allocation = new LeaveBlockBo();
                allocation.setAccrualCategory(accrualCategory.getAccrualCategory());

                if (thisEntryInterval.contains(LocalDate.now().toDate().getTime())) {
                    allocation.setLeaveLocalDate(LocalDate.now());
                } else {
                    allocation.setLeaveLocalDate(entry.getEndPeriodFullDateTime().toLocalDate().minusDays(1));
                }

                if (leaveLeaveEntry == null) {
                    allocation.setLeaveAmount(BigDecimal.ZERO);
                    allocation.setPrincipalId(principalId);
                    allocation.setLeaveBlockType("allocation");
                    leaveBlocks.add(LeaveBlockBo.to(allocation));
                } else {
                    //if(ObjectUtils.isNotNull(leaveLeaveEntry)) {
                    //if entry belongs to a time calendar, check the balances at the end date for the calendar year that ends
                    //within entry's interval.
                    //if entry belongs to a leave calendar, this empty block will simply duplicate and override
                    //the block created above.
                    allocation = new LeaveBlockBo();
                    allocation.setAccrualCategory(accrualCategory.getAccrualCategory());
                    allocation.setLeaveLocalDate(
                            leaveLeaveEntry.getEndPeriodFullDateTime().toLocalDate().minusDays(1));
                    allocation.setLeaveAmount(BigDecimal.ZERO);
                    allocation.setPrincipalId(principalId);
                    allocation.setLeaveBlockType("allocation");
                    leaveBlocks.add(LeaveBlockBo.to(allocation));
                }

                if (!leaveBlocks.isEmpty()) {
                    Collections.sort(leaveBlocks, new Comparator<LeaveBlock>() {

                        @Override
                        public int compare(LeaveBlock o1, LeaveBlock o2) {
                            return o1.getLeaveDateTime().compareTo(o2.getLeaveDateTime());
                        }

                    });
                }
            }

            for (LeaveBlock lb : leaveBlocks) {
                if (StringUtils.equals(lb.getRequestStatus(), HrConstants.REQUEST_STATUS.DISAPPROVED)
                        || StringUtils.equals(lb.getRequestStatus(), HrConstants.REQUEST_STATUS.DEFERRED)) {
                    continue;
                }
                AccrualCategory accrualCategory = lb.getAccrualCategoryObj();
                BigDecimal tally = accruedBalance.get(accrualCategory.getLmAccrualCategoryId());
                if (tally == null) {
                    tally = new BigDecimal(0);
                }
                tally = tally.add(lb.getLeaveAmount());
                AccrualCategoryRule asOfLeaveDateRule = lb.getAccrualCategoryRule();
                //AccrualCategoryRule asOfLeaveDateRule = HrServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(accrualCategory, lb.getLeaveLocalDate(), pha.getServiceLocalDate());

                //Employee overrides...
                if (ObjectUtils.isNotNull(asOfLeaveDateRule)) {
                    if (StringUtils.equals(asOfLeaveDateRule.getMaxBalFlag(), "Y")) {
                        if (StringUtils.isNotBlank(asOfLeaveDateRule.getActionAtMaxBalance())) {
                            if (ObjectUtils.isNotNull(asOfLeaveDateRule.getMaxBalanceActionFrequency())) {

                                if (maxBalanceViolations.get(asOfLeaveDateRule.getLmAccrualCategoryId()) == null) {
                                    maxBalanceViolations.put(asOfLeaveDateRule.getLmAccrualCategoryId(),
                                            new HashSet<LeaveBlock>());
                                }
                                BigDecimal maxBalance = asOfLeaveDateRule.getMaxBalance();

                                BigDecimal fte = HrServiceLocator.getJobService()
                                        .getFteSumForAllActiveLeaveEligibleJobs(principalId, LocalDate.now());
                                BigDecimal adjustedMaxBalance = maxBalance.multiply(fte);

                                BigDecimal maxAnnualCarryOver = null;
                                if (ObjectUtils.isNotNull(asOfLeaveDateRule.getMaxCarryOver()))
                                    maxAnnualCarryOver = new BigDecimal(asOfLeaveDateRule.getMaxCarryOver());

                                BigDecimal adjustedMaxAnnualCarryOver = null;
                                if (ObjectUtils.isNotNull(maxAnnualCarryOver)) {
                                    adjustedMaxAnnualCarryOver = maxAnnualCarryOver.multiply(fte);
                                }

                                List<? extends EmployeeOverrideContract> overrides = LmServiceLocator
                                        .getEmployeeOverrideService()
                                        .getEmployeeOverrides(principalId, lb.getLeaveLocalDate());
                                for (EmployeeOverrideContract override : overrides) {
                                    if (StringUtils.equals(override.getAccrualCategory(),
                                            lb.getAccrualCategory())) {
                                        //Do not pro-rate override values for FTE.
                                        if (StringUtils.equals(override.getOverrideType(), "MB"))
                                            adjustedMaxBalance = new BigDecimal(override.getOverrideValue());
                                        if (StringUtils.equals(override.getOverrideType(), "MAC"))
                                            adjustedMaxAnnualCarryOver = new BigDecimal(
                                                    override.getOverrideValue());
                                    }
                                }

                                boolean yearEndCandidate = false;
                                if (StringUtils.equals(asOfLeaveDateRule.getMaxBalanceActionFrequency(),
                                        HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)
                                        && adjustedMaxAnnualCarryOver != null) {
                                    yearEndCandidate = true;
                                }
                                //callers must determine if the violation exists on the calendar prior to the leave plan's calendar year end date.
                                if (tally.compareTo(adjustedMaxBalance) > 0
                                        || (yearEndCandidate && tally.compareTo(adjustedMaxAnnualCarryOver) > 0)) {
                                    if (maxBalanceViolations.get(asOfLeaveDateRule.getLmAccrualCategoryId())
                                            .isEmpty()) {
                                        maxBalanceViolations.get(asOfLeaveDateRule.getLmAccrualCategoryId())
                                                .add(lb);
                                    } else {
                                        Set<LeaveBlock> eligibleLeaveBlocks = maxBalanceViolations
                                                .get(asOfLeaveDateRule.getLmAccrualCategoryId());
                                        LeaveBlock previousInfraction = retreivePreviousInfraction(
                                                eligibleLeaveBlocks, lb, leavePeriodInterval, yearEndPeriodInterval,
                                                thisEntryInterval, asOfLeaveDateRule);

                                        if (previousInfraction != null) {
                                            eligibleLeaveBlocks.remove(previousInfraction);
                                        }
                                        /* Replace the leave block with the most recent infraction that occurred so long as the infraction takes place
                                         * before the end of the [underlying] leave period. This includes accrual categories that undergo a rule change
                                         * as the result of the employee crossing a service milestone.
                                         * Doing so will correctly trigger the action on timesheets that stagger a leave
                                         * period end date, since the block would exist on a leave period whose end date is not contained within
                                         * the time period. For on-demand frequencies, we want to replace the block with the latest no matter what.
                                         * Doing so will ensure that the latest rule is used to initiate the action.
                                         * 
                                         */
                                        eligibleLeaveBlocks.add(lb);
                                        maxBalanceViolations.put(asOfLeaveDateRule.getLmAccrualCategoryId(),
                                                eligibleLeaveBlocks);
                                    }
                                } else { // the leave amount (usage), as of lb's leave date, is under the balance limit.
                                    if (!maxBalanceViolations.get(asOfLeaveDateRule.getLmAccrualCategoryId())
                                            .isEmpty()) {
                                        //if there was a previous infraction, it must be removed so long as the leave date of lb lies within
                                        //the same period as the previous infraction.
                                        Set<LeaveBlock> eligibleLeaveBlocks = maxBalanceViolations
                                                .get(asOfLeaveDateRule.getLmAccrualCategoryId());
                                        LeaveBlock previousInfraction = retreivePreviousInfraction(
                                                eligibleLeaveBlocks, lb, leavePeriodInterval, yearEndPeriodInterval,
                                                thisEntryInterval, asOfLeaveDateRule);
                                        if (previousInfraction != null) {
                                            eligibleLeaveBlocks.remove(previousInfraction);
                                            maxBalanceViolations.put(asOfLeaveDateRule.getLmAccrualCategoryId(),
                                                    eligibleLeaveBlocks);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                accruedBalance.put(accrualCategory.getLmAccrualCategoryId(), tally);
            }
        }

        for (Entry<String, Set<LeaveBlock>> entries : maxBalanceViolations.entrySet()) {
            for (LeaveBlock lb : entries.getValue()) {
                AccrualCategoryRuleContract aRule = lb.getAccrualCategoryRule();
                eligibilities.get(aRule.getMaxBalanceActionFrequency()).add(lb);
            }
        }

        return eligibilities;
    }

    protected LeaveBlock retreivePreviousInfraction(Set<LeaveBlock> eligibleLeaveBlocks, LeaveBlock lb,
            Interval leavePeriodInterval, Interval yearEndPeriodInterval, Interval thisEntryInterval,
            AccrualCategoryRule asOfLeaveDateRule) {
        LeaveBlock tempLB = null;
        for (LeaveBlock block : eligibleLeaveBlocks) {
            AccrualCategoryRuleContract blockRule = block.getAccrualCategoryRule();
            if (StringUtils.equals(asOfLeaveDateRule.getLmAccrualCategoryRuleId(),
                    blockRule.getLmAccrualCategoryRuleId())) {
                if ((StringUtils.equals(asOfLeaveDateRule.getMaxBalanceActionFrequency(),
                        HrConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)
                        && StringUtils.equals(blockRule.getMaxBalanceActionFrequency(),
                                HrConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND))
                        || (leavePeriodInterval != null && leavePeriodInterval.contains(lb.getLeaveDateTime())
                                && leavePeriodInterval.contains(block.getLeaveDateTime()))
                        || (leavePeriodInterval == null && thisEntryInterval.contains(block.getLeaveDateTime())
                                && thisEntryInterval.contains(lb.getLeaveDateTime()))
                        || (StringUtils.equals(asOfLeaveDateRule.getMaxBalanceActionFrequency(),
                                HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)
                                && StringUtils.equals(blockRule.getMaxBalanceActionFrequency(),
                                        HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)
                                && (yearEndPeriodInterval == null
                                        || (yearEndPeriodInterval.contains(block.getLeaveDateTime())
                                                && yearEndPeriodInterval.contains(lb.getLeaveDateTime())))
                                //year end we replace if the two infractions are not within the same leave period.
                                //if yearEndPeriodInterval != null, the conditional for leave approve should have already evaluated to true.
                                //i.e. yearEndPeriodInterval != null will never be evaluated.
                                || block.getLeaveLocalDate().toDate()
                                        .before(thisEntryInterval.getStart().toDate()))) {
                    tempLB = block;
                    break;
                }
            } else if (StringUtils.equals(blockRule.getMaxBalanceActionFrequency(),
                    HrConstants.MAX_BAL_ACTION_FREQ.ON_DEMAND)
                    //always supersede on-demand action frequencies
                    || (StringUtils.equals(blockRule.getMaxBalanceActionFrequency(),
                            HrConstants.MAX_BAL_ACTION_FREQ.LEAVE_APPROVE)
                            && ((leavePeriodInterval != null
                                    && leavePeriodInterval.contains(block.getLeaveDateTime())
                                    && leavePeriodInterval.contains(lb.getLeaveDateTime()))
                                    || (leavePeriodInterval == null
                                            && thisEntryInterval.contains(block.getLeaveDateTime())
                                            && thisEntryInterval.contains(lb.getLeaveDateTime()))))
                    //leave approve is replaced only if the replacement lies within the same leave period.
                    || (StringUtils.equals(blockRule.getMaxBalanceActionFrequency(),
                            HrConstants.MAX_BAL_ACTION_FREQ.YEAR_END)
                            && (yearEndPeriodInterval == null
                                    || (yearEndPeriodInterval.contains(block.getLeaveDateTime())
                                            && yearEndPeriodInterval.contains(lb.getLeaveDateTime()))))
                    //year end is superseded only if the new rule goes into effect before the current leave plan calendar year end date.
                    || block.getLeaveLocalDate().toDate().before(thisEntryInterval.getStart().toDate())) {
                tempLB = block;
                break;
            }
        }
        return tempLB;
    }

}