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