View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.kpme.tklm.leave.accrual.service;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.joda.time.DateTime;
22  import org.joda.time.DateTimeConstants;
23  import org.joda.time.Interval;
24  import org.joda.time.LocalDate;
25  import org.kuali.kpme.core.api.accrualcategory.AccrualCategory;
26  import org.kuali.kpme.core.api.accrualcategory.AccrualCategoryContract;
27  import org.kuali.kpme.core.api.accrualcategory.AccrualEarnInterval;
28  import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRule;
29  import org.kuali.kpme.core.api.accrualcategory.rule.AccrualCategoryRuleContract;
30  import org.kuali.kpme.core.api.assignment.Assignment;
31  import org.kuali.kpme.core.api.calendar.Calendar;
32  import org.kuali.kpme.core.api.calendar.entry.CalendarEntry;
33  import org.kuali.kpme.core.api.earncode.EarnCodeContract;
34  import org.kuali.kpme.core.api.job.Job;
35  import org.kuali.kpme.core.api.job.JobContract;
36  import org.kuali.kpme.core.api.leaveplan.LeavePlan;
37  import org.kuali.kpme.core.api.leaveplan.LeavePlanContract;
38  import org.kuali.kpme.core.api.principal.PrincipalHRAttributes;
39  import org.kuali.kpme.core.service.HrServiceLocator;
40  import org.kuali.kpme.core.util.HrConstants;
41  import org.kuali.kpme.core.util.HrContext;
42  import org.kuali.kpme.core.util.TKUtils;
43  import org.kuali.kpme.tklm.api.leave.accrual.AccrualService;
44  import org.kuali.kpme.tklm.api.leave.accrual.PrincipalAccrualRanContract;
45  import org.kuali.kpme.tklm.api.leave.accrual.RateRangeContract;
46  import org.kuali.kpme.tklm.api.leave.block.LeaveBlock;
47  import org.kuali.kpme.tklm.api.leave.timeoff.SystemScheduledTimeOffContract;
48  import org.kuali.kpme.tklm.common.LMConstants;
49  import org.kuali.kpme.tklm.leave.accrual.RateRange;
50  import org.kuali.kpme.tklm.leave.accrual.RateRangeAggregate;
51  import org.kuali.kpme.tklm.leave.block.LeaveBlockBo;
52  import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
53  import org.kuali.kpme.tklm.leave.workflow.LeaveCalendarDocumentHeader;
54  
55  import java.math.BigDecimal;
56  import java.util.ArrayList;
57  import java.util.Collections;
58  import java.util.HashMap;
59  import java.util.HashSet;
60  import java.util.List;
61  import java.util.Map;
62  import java.util.Set;
63  
64  public class AccrualServiceImpl implements AccrualService {
65      private static final Logger LOG = Logger.getLogger(AccrualServiceImpl.class);
66  
67  	@Override
68  	public void runAccrual(String principalId) {
69  		DateTime startDate = getStartAccrualDate(principalId);
70  		DateTime endDate = getEndAccrualDate(principalId);
71  
72  		LOG.info("AccrualServiceImpl.runAccrual() STARTED with Principal: "+principalId);
73  		runAccrual(principalId,startDate,endDate, true);
74  		
75  	}
76  	
77  	@Override
78  	public void runAccrual(String principalId, DateTime startDate, DateTime endDate, boolean recordRanData) {
79  		runAccrual(principalId, startDate, endDate, recordRanData, HrContext.getPrincipalId());
80  	}
81  
82  	@SuppressWarnings("unchecked")
83  	@Override
84  	public void runAccrual(String principalId, DateTime startDate, DateTime endDate, boolean recordRanData, String runAsPrincipalId) {
85  		List<LeaveBlock> accrualLeaveBlocks = new ArrayList<LeaveBlock>();
86  		Map<String, BigDecimal> accumulatedAccrualCatToAccrualAmounts = new HashMap<String,BigDecimal>();
87  		Map<String, BigDecimal> accumulatedAccrualCatToNegativeAccrualAmounts = new HashMap<String,BigDecimal>();
88  		
89  		if (startDate != null && endDate != null) {
90              LOG.info("AccrualServiceImpl.runAccrual() STARTED with Principal: "+principalId+" Start: "+startDate.toString()+" End: "+endDate.toString());
91  		}
92  		if(startDate.isAfter(endDate)) {
93  			LOG.error("Start Date " + startDate.toString() + " should not be later than End Date " + endDate.toString());
94  			return;
95  //			throw new RuntimeException("Start Date " + startDate.toString() + " should not be later than End Date " + endDate.toString());
96  		}
97  		//Inactivate all previous accrual-generated entries for this span of time
98  		deactivateOldAccruals(principalId, startDate, endDate, runAsPrincipalId);
99  		
100 		//Build a rate range aggregate with appropriate information for this period of time detailing Rate Ranges for job
101 		//entries for this range of time
102 		RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate);
103 		PrincipalHRAttributes phra = null;
104 		PrincipalHRAttributes endPhra = null;
105 		LeavePlanContract lp = null;
106 		List<? extends AccrualCategoryContract> accrCatList = null;
107 		
108 		//Iterate over every day in span 
109 		DateTime currentDate = startDate;
110 		while (!currentDate.isAfter(endDate)) {
111 			RateRange currentRange = rrAggregate.getRateOnDate(currentDate);
112 			if(currentRange == null) {
113 				currentDate = currentDate.plusDays(1);
114 				continue;
115 			}
116 			
117 			phra = currentRange.getPrincipalHRAttributes();
118 			if(phra == null || currentDate.toLocalDate().isBefore(phra.getServiceLocalDate())) {
119 				currentDate = currentDate.plusDays(1);
120 				continue;
121 			}
122 			
123 			
124 	// use the effectiveDate of this principalHRAttribute to search for inactive entries for this principalId
125 	// If there's an inactive entry, it means the job is going to end on the effectiveDate of the inactive entry
126 	// used for minimumPercentage and proration
127 			endPhra = currentRange.getEndPrincipalHRAttributes();
128 			if(endPhra != null && currentDate.toLocalDate().isAfter(endPhra.getEffectiveLocalDate())) {
129 				currentDate = currentDate.plusDays(1);
130 				continue;
131 			}
132 			
133 	// if the date range is before the service date of this employee, do not calculate accrual
134 			if(endDate.toLocalDate().isBefore(phra.getServiceLocalDate())) {
135 				return;
136 			}
137 			lp = currentRange.getLeavePlan();
138 			accrCatList = currentRange.getAcList();
139 			// if the employee status is changed, create an empty leave block on the currentDate
140 			if(currentRange.isStatusChanged()) {
141 				this.createEmptyLeaveBlockForStatusChange(principalId, accrualLeaveBlocks, currentDate.toLocalDate());
142 			}
143 			// if no job found for the employee on the currentDate, do nothing
144 			if(CollectionUtils.isEmpty(currentRange.getJobs())) {
145 				currentDate = currentDate.plusDays(1);
146 				continue;
147 			}
148 			
149 			BigDecimal ftePercentage = currentRange.getAccrualRatePercentageModifier();
150 			BigDecimal totalOfStandardHours = currentRange.getStandardHours();
151 			boolean fullFteGranted = false;
152 			for(AccrualCategoryContract anAC : accrCatList) {
153 				if(anAC == null)
154 					continue;
155 				
156 				fullFteGranted = false;
157 				if(!currentDate.toLocalDate().isBefore(phra.getEffectiveLocalDate()) && !anAC.getAccrualEarnInterval().equals("N")) {   	// "N" means no accrual
158 					boolean prorationFlag = this.isProrationFlag(anAC.getProration());
159 					// get the accrual rule 
160 					AccrualCategoryRuleContract currentAcRule = this.getRuleForAccrualCategory(currentRange.getAcRuleList(), anAC);
161 				
162 					// check if accrual category rule changed
163 					if(currentAcRule != null) {
164 						DateTime ruleStartDate = getRuleStartDate(currentAcRule.getServiceUnitOfTime(), phra.getServiceLocalDate(), currentAcRule.getStart());
165 						DateTime previousIntervalDay = this.getPrevIntervalDate(ruleStartDate, anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
166 						DateTime nextIntervalDay = this.getNextIntervalDate(ruleStartDate, anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
167 						
168 						RateRangeContract previousRange = rrAggregate.getRateOnDate(previousIntervalDay);
169 						AccrualCategoryRuleContract previousAcRule = null;
170 						if(previousRange != null) {
171 							previousAcRule = this.getRuleForAccrualCategory(previousRange.getAcRuleList(), anAC);
172 						}
173 						// rule changed
174 						if(previousAcRule != null && !previousAcRule.getLmAccrualCategoryRuleId().equals(currentAcRule.getLmAccrualCategoryRuleId())) {
175 							if(currentDate.toLocalDate().compareTo(previousIntervalDay.toLocalDate()) >= 0 
176 									&& currentDate.toLocalDate().compareTo(nextIntervalDay.toLocalDate()) <= 0) {
177 								int workDaysInBetween = TKUtils.getWorkDays(ruleStartDate, nextIntervalDay);
178 								boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(), 
179 												anAC.getAccrualEarnInterval(), workDaysInBetween, nextIntervalDay,
180 												phra.getPayCalendar(), rrAggregate.getCalEntryMap());
181 								if(prorationFlag) {
182 									if(minReachedFlag) {
183 										// min reached, proration=true, rule changed, then use actual work days of currentRule for calculation
184 										// so nothing special needs to be done here								
185 									} else {
186 										//minimum percentage NOT reached, proration = true, rule changed, then use previousRule for the whole pay period
187 										currentAcRule = previousAcRule;
188 									}
189 								} else {
190 									if(minReachedFlag) {
191 										// min reached, proration = false, rule changed, then accrual the whole fte of the new rule for this pay interval
192 										accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), currentAcRule.getAccrualRate());
193 										fullFteGranted = true;
194 									} else {
195 										//min NOT reached, proration = false, rule changed, then accrual the whole fte of the previous rule for this pay interval
196 										accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), previousAcRule.getAccrualRate());
197 										fullFteGranted = true;
198 									}
199 								}
200 							}
201 						}
202 					}
203 					
204 					// check for first pay period of principal attributes considering minimum percentage and proration	
205 					DateTime firstIntervalDate = this.getNextIntervalDate(phra.getEffectiveLocalDate().toDateTimeAtStartOfDay(), anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
206 					if(!currentDate.toLocalDate().isBefore(phra.getEffectiveLocalDate()) 
207 							&& !currentDate.toLocalDate().isAfter(firstIntervalDate.toLocalDate())) {
208 						int workDaysInBetween = TKUtils.getWorkDays(phra.getEffectiveLocalDate().toDateTimeAtStartOfDay(), firstIntervalDate);
209 						boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(),  anAC.getAccrualEarnInterval(), 
210 									workDaysInBetween, firstIntervalDate,
211 									phra.getPayCalendar(), rrAggregate.getCalEntryMap());
212 						
213 						if(prorationFlag) {
214 							if(minReachedFlag) {
215 								// minimum reached, proration = true, first pay period, then use actual work days of currentRule for calculation
216 								// so nothing special needs to be done here
217 							} else {
218 								// min NOT reached, proration = true, first pay period, then no accrual for this pay period
219 								accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
220 								accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
221 								continue;
222 							}
223 						} else {
224 							if(minReachedFlag && currentAcRule != null) {
225 								//  minimum reached, proration = false, first pay period, then accrual the whole fte of current AC rule for this pay interval
226 								accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), currentAcRule.getAccrualRate());
227 								fullFteGranted = true;
228 							} else {
229 								// min NOT reached, proration = false, first pay period, then no accrual for this pay period
230 								accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
231 								accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
232 								continue;
233 							}
234 						}
235 					}
236 					// last accrual interval
237 					if(endPhra != null) {	// the employment is going to end on the effectiveDate of enPhra
238 						DateTime previousIntervalDate = this.getPrevIntervalDate(endPhra.getEffectiveLocalDate().toDateTimeAtStartOfDay(), anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
239 						// currentDate is between the end date and the last interval date, so we are in the last interval
240 						if(!currentDate.toLocalDate().isAfter(endPhra.getEffectiveLocalDate()) 
241 								&& currentDate.toLocalDate().isAfter(previousIntervalDate.toLocalDate())) {
242 							DateTime lastIntervalDate = this.getNextIntervalDate(endPhra.getEffectiveLocalDate().toDateTimeAtStartOfDay(), anAC.getAccrualEarnInterval(),  phra.getPayCalendar(), rrAggregate.getCalEntryMap());
243 							int workDaysInBetween = TKUtils.getWorkDays(previousIntervalDate, endPhra.getEffectiveLocalDate().toDateTimeAtStartOfDay());
244 							boolean minReachedFlag = minimumPercentageReachedForPayPeriod(anAC.getMinPercentWorked(),  anAC.getAccrualEarnInterval(), 
245 										workDaysInBetween, lastIntervalDate,
246 										phra.getPayCalendar(), rrAggregate.getCalEntryMap());
247 							if(prorationFlag) {
248 								if(minReachedFlag) {
249 									// minimum reached, proration = true, first pay period, then use actual work days of currentRule for calculation
250 									// so nothing special needs to be done here
251 								} else {
252 									// min NOT reached, proration = true, first pay period, then no accrual for this pay period
253 									accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
254 									accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
255 									continue;
256 								}
257 							} else {
258 								if(minReachedFlag) {
259 									//  minimum reached, proration = false, first pay period, then accrual the whole fte of current AC rule for this pay interval
260 									accumulatedAccrualCatToAccrualAmounts.put(anAC.getLmAccrualCategoryId(), currentAcRule.getAccrualRate());
261 									fullFteGranted = true;
262 								} else {
263 									// min NOT reached, proration = false, first pay period, then no accrual for this pay period
264 									accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
265 									accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
266 									continue;
267 								}
268 							}
269 						}
270 					}
271 										
272 					if(currentAcRule == null) {
273 						accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
274 						accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());
275 						continue;
276 					}
277 					
278 					//Fetch the accrual rate based on rate range for today(Rate range is the accumulated list of jobs and accrual rate for today)
279 					BigDecimal accrualRate = currentAcRule.getAccrualRate();
280 					int numberOfWorkDays = this.getWorkDaysInInterval(currentDate, anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap());
281 					BigDecimal dayRate = numberOfWorkDays > 0 ? accrualRate.divide(new BigDecimal(numberOfWorkDays), 6, BigDecimal.ROUND_HALF_UP) : new BigDecimal(0);
282 				
283 					//get not eligible for accrual hours based on leave block on this day
284 					BigDecimal noAccrualHours = getNotEligibleForAccrualHours(principalId, currentDate.toLocalDate());
285 					if(noAccrualHours != null && noAccrualHours.compareTo(BigDecimal.ZERO) != 0 && totalOfStandardHours.compareTo(BigDecimal.ZERO) != 0) {
286 						BigDecimal dayHours = totalOfStandardHours.divide(new BigDecimal(5), 6, BigDecimal.ROUND_HALF_UP);
287 						// if leave hours on the day is more than the standard hours, use the standard hour to calculate the adjustment
288 						if(noAccrualHours.abs().compareTo(dayHours.abs()) == 1) {
289 							noAccrualHours = dayHours.abs().negate();
290 						}
291 						BigDecimal noAccrualRate = dayRate.multiply(noAccrualHours.divide(dayHours));
292 						this.calculateHours(anAC.getLmAccrualCategoryId(), ftePercentage, noAccrualRate, accumulatedAccrualCatToNegativeAccrualAmounts);
293 					}
294 					
295 					// only accrual on work days
296 					if(!TKUtils.isWeekend(currentDate) && !fullFteGranted) {
297 						//Add to total accumulatedAccrualCatToAccrualAmounts
298 						//use rule and ftePercentage to calculate the hours				
299 						this.calculateHours(anAC.getLmAccrualCategoryId(), ftePercentage, dayRate, accumulatedAccrualCatToAccrualAmounts);
300 					}					
301 					//Determine if we are at the accrual earn interval in the span, if so add leave block for accumulated accrual amount to list
302 					//and reset accumulatedAccrualCatToAccrualAmounts and accumulatedAccrualCatToNegativeAccrualAmounts for this accrual category
303 					if(this.isDateAnIntervalDate(currentDate.toLocalDate(), anAC.getAccrualEarnInterval(), phra.getPayCalendar(), rrAggregate.getCalEntryMap())) {
304 						BigDecimal acHours = accumulatedAccrualCatToAccrualAmounts.get(anAC.getLmAccrualCategoryId());
305 						
306 						if(acHours != null) {
307 							createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), acHours, anAC, null, true, currentRange.getLeaveCalendarDocumentId(), null);
308 							accumulatedAccrualCatToAccrualAmounts.remove(anAC.getLmAccrualCategoryId());	// reset accumulatedAccrualCatToAccrualAmounts
309 							fullFteGranted = false;
310 						}
311 						
312 						BigDecimal adjustmentHours = accumulatedAccrualCatToNegativeAccrualAmounts.get(anAC.getLmAccrualCategoryId());
313 						if(adjustmentHours != null && adjustmentHours.compareTo(BigDecimal.ZERO) != 0) {
314 							// do not create leave block if the ajustment amount is 0
315 							createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), adjustmentHours, anAC, null, false, currentRange.getLeaveCalendarDocumentId(), null);
316 							accumulatedAccrualCatToNegativeAccrualAmounts.remove(anAC.getLmAccrualCategoryId());	// reset accumulatedAccrualCatToNegativeAccrualAmounts
317 						}
318 					}			
319 				}
320 			}
321 			//Determine if today is a system scheduled time off and accrue holiday if so.
322 			SystemScheduledTimeOffContract ssto = currentRange.getSysScheTimeOff();
323 			if(ssto != null) {
324 				AccrualCategory anAC = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(ssto.getAccrualCategory(), ssto.getEffectiveLocalDate());
325 				if(anAC == null) {
326 					LOG.error("Cannot find Accrual Category for system scheduled time off " + ssto.getLmSystemScheduledTimeOffId());
327 					return;
328 //					throw new RuntimeException("Cannot find Accrual Category for system scheduled time off " + ssto.getLmSystemScheduledTimeOffId());
329 				}
330 				BigDecimal hrs = ssto.getAmountofTime().multiply(ftePercentage);
331 				// system scheduled time off leave block
332 				createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), hrs, anAC, ssto.getLmSystemScheduledTimeOffId(), true, currentRange.getLeaveCalendarDocumentId(), null);
333 				// we only need to create usage leave block for ssto if there is a scheduled time off date
334 				if(ssto.getScheduledTimeOffDate() != null) {
335 					// usage leave block with negative amount. Assign primary leave assignment information to ssto usage leave block
336 					createLeaveBlock(principalId, accrualLeaveBlocks, ssto.getScheduledTimeOffLocalDate(), 
337 								hrs.negate(), anAC, ssto.getLmSystemScheduledTimeOffId(), true, currentRange.getLeaveCalendarDocumentId(), currentRange.getPrimaryLeaveAssignmentId());
338 				}
339 			}
340 			// if today is the last day of the employment, create leave blocks if there's any hours available
341 			if(endPhra != null && currentDate.toLocalDate().equals(endPhra.getEffectiveLocalDate())){
342 				// accumulated accrual amount
343 				if(!accumulatedAccrualCatToAccrualAmounts.isEmpty()) {
344 					for(Map.Entry<String, BigDecimal> entry : accumulatedAccrualCatToAccrualAmounts.entrySet()) {
345 						if(entry.getValue() != null && entry.getValue().compareTo(BigDecimal.ZERO) != 0) {
346 							AccrualCategory anAC = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(entry.getKey());
347 							createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), entry.getValue(), anAC, null, true, currentRange.getLeaveCalendarDocumentId(), null);
348 						}
349 					}
350 					accumulatedAccrualCatToAccrualAmounts = new HashMap<String,BigDecimal>();	// reset accumulatedAccrualCatToAccrualAmounts
351 				}
352 				// negative/adjustment accrual amount
353 				if(!accumulatedAccrualCatToNegativeAccrualAmounts.isEmpty()) {
354 					for(Map.Entry<String, BigDecimal> entry : accumulatedAccrualCatToNegativeAccrualAmounts.entrySet()) {
355 						if(entry.getValue() != null && entry.getValue().compareTo(BigDecimal.ZERO) != 0) {
356 							AccrualCategory anAC = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(entry.getKey());
357 							createLeaveBlock(principalId, accrualLeaveBlocks, currentDate.toLocalDate(), entry.getValue(), anAC, null, true, currentRange.getLeaveCalendarDocumentId(), null);
358 						}
359 					}
360 					accumulatedAccrualCatToNegativeAccrualAmounts = new HashMap<String,BigDecimal>();	// reset accumulatedAccrualCatToNegativeAccrualAmounts
361 				}
362 				phra = null;	// reset principal attribute so new value will be retrieved
363 				endPhra = null;	// reset end principal attribute so new value will be retrieved
364 			}
365 			
366 			currentDate = currentDate.plusDays(1);
367 		}
368 		
369 		//Save accrual leave blocks at the very end
370 		LmServiceLocator.getLeaveBlockService().saveLeaveBlocks(accrualLeaveBlocks);
371 		
372 		// record timestamp of this accrual run in database
373 		if(recordRanData) {
374 			LmServiceLocator.getPrincipalAccrualRanService().updatePrincipalAccrualRanInfo(principalId);
375 		}
376 		
377 	}
378 	
379 	private void deactivateOldAccruals(String principalId, DateTime startDate, DateTime endDate, String runAsPrincipalId) {
380 		List<LeaveBlock> previousLB = LmServiceLocator.getLeaveBlockService().getAccrualGeneratedLeaveBlocks(principalId, startDate.toLocalDate(), endDate.toLocalDate());
381 		List<LeaveBlock> sstoAccrualList = new ArrayList<LeaveBlock>();
382 		List<LeaveBlock> sstoUsageList = new ArrayList<LeaveBlock>();
383 		
384 		for(LeaveBlock lb : previousLB) {
385 			if(StringUtils.isNotEmpty(lb.getScheduleTimeOffId())) {
386 				if(lb.getLeaveAmount().compareTo(BigDecimal.ZERO) > 0) {
387 					sstoAccrualList.add(lb);
388 				} else if(lb.getLeaveAmount().compareTo(BigDecimal.ZERO) < 0) {
389 					sstoUsageList.add(lb);
390 				}
391 			} else {
392 				if(!(StringUtils.equals(lb.getLeaveBlockType(),LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER) ||
393 								StringUtils.equals(lb.getLeaveBlockType(),LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT))) {
394 					LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(lb.getLmLeaveBlockId(), runAsPrincipalId);
395                 }
396 			}
397 		}
398 		
399 		for(LeaveBlock accrualLb : sstoAccrualList) {
400 			for(LeaveBlock usageLb : sstoUsageList) {
401 				// both usage and accrual ssto leave blocks are there, so the ssto accural is not banked, removed both leave blocks
402 				// 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
403 				if(accrualLb.getScheduleTimeOffId().equals(usageLb.getScheduleTimeOffId())) {	
404 					LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(accrualLb.getLmLeaveBlockId(), runAsPrincipalId);
405 					LmServiceLocator.getLeaveBlockService().deleteLeaveBlock(usageLb.getLmLeaveBlockId(), runAsPrincipalId);
406 				}
407 			}
408 		}
409 		
410 	}
411 	
412 	private BigDecimal getNotEligibleForAccrualHours(String principalId, LocalDate currentDate) {
413 		BigDecimal hours = BigDecimal.ZERO;
414 		// check if there's any manual not-eligible-for-accrual leave blocks, use the hours of the leave block to adjust accrual calculation 
415 		List<LeaveBlock> lbs = LmServiceLocator.getLeaveBlockService().getNotAccrualGeneratedLeaveBlocksForDate(principalId, currentDate);
416 		for(LeaveBlock lb : lbs) {
417 			EarnCodeContract ec = HrServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), currentDate);
418 			if(ec == null) {
419 				LOG.error("Cannot find Earn Code for Leave block " + lb.getLmLeaveBlockId());
420 				return null;
421 //				throw new RuntimeException("Cannot find Earn Code for Leave block " + lb.getLmLeaveBlockId());
422 			}
423 			if(ec.getEligibleForAccrual().equals("N") 
424 					&& ec.getAccrualBalanceAction().equals(HrConstants.ACCRUAL_BALANCE_ACTION.USAGE)
425 					&& lb.getLeaveAmount().compareTo(BigDecimal.ZERO) != 0) {
426 				hours = hours.add(lb.getLeaveAmount());
427 			}		
428 		}
429 		return hours;
430 	}
431 	
432 	private void createLeaveBlock(String principalId, List<LeaveBlock> accrualLeaveBlocks,
433 			LocalDate leaveDate, BigDecimal hrs, AccrualCategoryContract anAC, String sysSchTimeOffId,
434 			boolean createZeroLeaveBlock, String leaveDocId, String primaryAssignmentId) {
435 		// Replacing Leave Code to earn code - KPME 1634
436 		EarnCodeContract ec = HrServiceLocator.getEarnCodeService().getEarnCode(anAC.getEarnCode(), anAC.getEffectiveLocalDate());
437 		if(ec == null) {
438 //			throw new RuntimeException("Cannot find Earn Code for Accrual category " + anAC.getAccrualCategory());
439 			LOG.error("Cannot find Earn Code for Accrual category " + anAC.getAccrualCategory());
440 			return;
441 		}
442 		// use rounding option and fract time allowed of Leave Code to round the leave block hours
443 		BigDecimal roundedHours = HrServiceLocator.getEarnCodeService().roundHrsWithEarnCode(hrs, ec);
444 		if(!createZeroLeaveBlock && roundedHours.compareTo(BigDecimal.ZERO) == 0) {
445 			return;	// do not create leave block with zero amount
446 		}
447 		LeaveBlockBo aLeaveBlock = new LeaveBlockBo();
448 		aLeaveBlock.setAccrualCategory(anAC.getAccrualCategory());
449 		aLeaveBlock.setLeaveLocalDate(leaveDate);
450 		aLeaveBlock.setPrincipalId(principalId);
451 		//More than one earn code can be associated with an accrual category. Which one does this get?
452 		aLeaveBlock.setEarnCode(anAC.getEarnCode());
453 		aLeaveBlock.setAccrualGenerated(true);
454 		aLeaveBlock.setBlockId(0L);
455 		aLeaveBlock.setScheduleTimeOffId(sysSchTimeOffId);
456 		aLeaveBlock.setLeaveAmount(roundedHours);
457 		aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
458 		aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.APPROVED);
459 		aLeaveBlock.setDocumentId(leaveDocId);
460 		
461 		if(StringUtils.isNotBlank(primaryAssignmentId)) {
462             Assignment primAssignment = HrServiceLocator.getAssignmentService().getAssignment(primaryAssignmentId);
463 			if(primAssignment != null) {
464                 aLeaveBlock.setGroupKeyCode(primAssignment.getGroupKeyCode());
465 				aLeaveBlock.setWorkArea(primAssignment.getWorkArea());
466 				aLeaveBlock.setJobNumber(primAssignment.getJobNumber());
467 		        aLeaveBlock.setTask(primAssignment.getTask());
468 			}		
469 		}
470 		
471 		accrualLeaveBlocks.add(LeaveBlockBo.to(aLeaveBlock));
472 		
473 	}
474 	
475 	private void createEmptyLeaveBlockForStatusChange(String principalId, List<LeaveBlock> accrualLeaveBlocks, LocalDate leaveDate) {
476 		LeaveBlockBo aLeaveBlock = new LeaveBlockBo();
477 		aLeaveBlock.setAccrualCategory(null);
478 		aLeaveBlock.setLeaveLocalDate(leaveDate);
479 		aLeaveBlock.setPrincipalId(principalId);
480 		aLeaveBlock.setEarnCode(LMConstants.STATUS_CHANGE_EARN_CODE);	// fake leave code
481 		aLeaveBlock.setAccrualGenerated(true);
482 		aLeaveBlock.setBlockId(0L);
483 		aLeaveBlock.setScheduleTimeOffId(null);
484 		aLeaveBlock.setLeaveAmount(BigDecimal.ZERO);
485 		aLeaveBlock.setLeaveBlockType(LMConstants.LEAVE_BLOCK_TYPE.ACCRUAL_SERVICE);
486 		aLeaveBlock.setRequestStatus(HrConstants.REQUEST_STATUS.APPROVED);
487 		
488 		accrualLeaveBlocks.add(LeaveBlockBo.to(aLeaveBlock));
489 		
490 	}
491 
492 	private void calculateHours(String accrualCategoryId, BigDecimal fte, BigDecimal rate, Map<String, BigDecimal> accumulatedAmounts ) {
493 		BigDecimal hours = rate.multiply(fte);
494 		BigDecimal oldHours = accumulatedAmounts.get(accrualCategoryId);
495 		BigDecimal newHours = oldHours == null ? hours : hours.add(oldHours);
496 		accumulatedAmounts.put(accrualCategoryId, newHours);
497 	}
498 	
499 	public DateTime getStartAccrualDate(String principalId){
500 		return null;
501 	}
502 	
503 	public DateTime getEndAccrualDate(String principalId){
504 		return null;
505 	}
506 
507 	@Override
508 	public void runAccrual(List<String> principalIds) {
509 		for(String principalId : principalIds){
510 			runAccrual(principalId);
511 		}
512 	}
513 	
514 	private boolean isDateAnIntervalDate(LocalDate aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
515 		if(earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
516 			return isDateAtPayCalInterval(aDate, earnInterval, payCalName, aMap);
517 		} else {
518 			return this.isDateAtEarnInterval(aDate, earnInterval);
519 		}
520 	}
521 	
522 	private boolean isDateAtPayCalInterval(LocalDate aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
523 		if(StringUtils.isNotEmpty(payCalName) 
524 				&& !aMap.isEmpty()
525 				&& earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {	// only used for ac earn interval == pay calendar
526 			List<CalendarEntry> entryList = aMap.get(payCalName);
527 			if(CollectionUtils.isNotEmpty(entryList)) {
528 				for(CalendarEntry anEntry : entryList) {
529 					// 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
530 					LocalDate endDate = anEntry.getEndPeriodFullDateTime().toLocalDate().minusDays(1);
531 					if(aDate.compareTo(endDate) == 0) {
532 						return true;
533 					}
534 				}
535 			}
536 		}
537 		return false;
538 	}
539 	
540 	@Override
541 	public boolean isDateAtEarnInterval(LocalDate aDate, String earnInterval) {
542 		boolean atEarnInterval = false;
543         AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
544 		if (accrualEarnInterval != null) {
545 			if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
546 				atEarnInterval = true;
547 			} else if(AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
548 				// figure out if the day is a Saturday
549 				if (aDate.getDayOfWeek() == DateTimeConstants.SATURDAY) {
550 					atEarnInterval = true;
551 				}
552 			} else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
553 				// either the 15th or the last day of the month
554 				if (aDate.getDayOfMonth() == 15 || aDate.getDayOfMonth() == aDate.dayOfMonth().getMaximumValue()) {
555 					atEarnInterval = true;
556 				}
557 			} else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
558 				// the last day of the month
559 				if (aDate.getDayOfMonth() == aDate.dayOfMonth().getMaximumValue()) {
560 					atEarnInterval = true;
561 				}
562 			} else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
563 				// the last day of the year
564 				if (aDate.getDayOfYear() == aDate.dayOfYear().getMaximumValue()) {
565 					atEarnInterval = true;
566 				}
567 			} else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
568 				// no calculation
569 			}
570 		}
571 		return atEarnInterval;
572 	}
573 
574 
575 	@Override
576 	public RateRangeAggregate buildRateRangeAggregate(String principalId, DateTime startDate, DateTime endDate) {
577 		RateRangeAggregate rrAggregate = new RateRangeAggregate();
578 		List<RateRange> rateRangeList = new ArrayList<RateRange>();	
579 		// get all active jobs that are effective before the endDate
580 		List<Job> activeJobs = HrServiceLocator.getJobService().getAllActiveLeaveJobs(principalId, endDate.toLocalDate());
581 		List<Job> inactiveJobs = HrServiceLocator.getJobService().getAllInActiveLeaveJobsInRange(principalId, endDate.toLocalDate());
582 		
583 		List<PrincipalHRAttributes> phaList = HrServiceLocator.getPrincipalHRAttributeService().getAllActivePrincipalHrAttributesForPrincipalId(principalId, endDate.toLocalDate());
584 		List<PrincipalHRAttributes> inactivePhaList = HrServiceLocator.getPrincipalHRAttributeService().getAllInActivePrincipalHrAttributesForPrincipalId(principalId, endDate.toLocalDate());
585 		
586 		if(activeJobs.isEmpty() || phaList.isEmpty()) {
587 			return rrAggregate;
588 		}
589 		
590 		Set<String> phaLpSet = new HashSet<String>();
591 		Set<String> calNameSet = new HashSet<String>();
592 		if(CollectionUtils.isNotEmpty(phaList)) {
593 			for(PrincipalHRAttributes pha : phaList) {
594 				phaLpSet.add(pha.getLeavePlan());
595 				calNameSet.add(pha.getPayCalendar());
596 			}
597 		}
598 		
599 		List<LeavePlan> activeLpList = new ArrayList<LeavePlan> ();
600 		List<LeavePlan> inactiveLpList = new ArrayList<LeavePlan> ();
601 		for(String lpString : phaLpSet) {
602 			List<LeavePlan> aList = HrServiceLocator.getLeavePlanService().getAllActiveLeavePlan(lpString, endDate.toLocalDate());
603 			activeLpList.addAll(aList);
604 			
605 			aList = HrServiceLocator.getLeavePlanService().getAllInActiveLeavePlan(lpString, endDate.toLocalDate());
606 			inactiveLpList.addAll(aList);
607 		}
608 		
609 		// get all pay calendar entries for this employee. used to determine interval dates
610 		Map<String, List<CalendarEntry>> calEntryMap = new HashMap<String, List<CalendarEntry>>();
611 		for(String calName : calNameSet) {
612             Calendar aCal = HrServiceLocator.getCalendarService().getCalendarByGroup(calName);
613 			if(aCal != null) {
614 				List<CalendarEntry> aList = HrServiceLocator.getCalendarEntryService().getAllCalendarEntriesForCalendarId(aCal.getHrCalendarId());
615 				Collections.sort(aList);
616 				calEntryMap.put(calName, aList);
617 			}
618 		}
619 		rrAggregate.setCalEntryMap(calEntryMap);		
620 		
621 		Set<String> lpStringSet = new HashSet<String>();
622 		if(CollectionUtils.isNotEmpty(activeLpList)) {
623 			for(LeavePlan lp : activeLpList) {
624 				lpStringSet.add(lp.getLeavePlan());
625 			}
626 		}
627 		List<SystemScheduledTimeOffContract> sstoList = new ArrayList<SystemScheduledTimeOffContract>();
628 		for(String lpString : lpStringSet) {
629 			List<? extends SystemScheduledTimeOffContract> aList =LmServiceLocator.getSysSchTimeOffService().getSystemScheduledTimeOffsForLeavePlan(startDate.toLocalDate(), endDate.toLocalDate(), lpString);
630 			if(CollectionUtils.isNotEmpty(aList)) {
631 				sstoList.addAll(aList);
632 			}
633 		}
634 		
635 		List<AccrualCategory> activeAccrCatList = new ArrayList<AccrualCategory>();
636 		List<AccrualCategory> inactiveAccrCatList = new ArrayList<AccrualCategory>();
637 		for(String lpString : lpStringSet) {
638 			List<AccrualCategory> aList = HrServiceLocator.getAccrualCategoryService().getActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate.toLocalDate());
639 			if(CollectionUtils.isNotEmpty(aList)) {
640 				activeAccrCatList.addAll(aList);
641 			}
642 			
643 			aList = HrServiceLocator.getAccrualCategoryService().getInActiveLeaveAccrualCategoriesForLeavePlan(lpString, endDate.toLocalDate());
644 			if(CollectionUtils.isNotEmpty(aList)) {
645 				inactiveAccrCatList.addAll(aList);
646 			}
647 		}
648 		
649 		List<AccrualCategoryRule> activeRuleList = new ArrayList<AccrualCategoryRule>();
650 		List<AccrualCategoryRule> inactiveRuleList = new ArrayList<AccrualCategoryRule>();
651 		for(AccrualCategory ac : activeAccrCatList) {
652 			List<AccrualCategoryRule> aRuleList = HrServiceLocator.getAccrualCategoryRuleService().getActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId());
653 			activeRuleList.addAll(aRuleList);
654 			
655 			aRuleList = HrServiceLocator.getAccrualCategoryRuleService().getInActiveRulesForAccrualCategoryId(ac.getLmAccrualCategoryId());
656 			inactiveRuleList.addAll(aRuleList);
657 		}
658 		
659 		List<LeaveCalendarDocumentHeader> lcDocList = LmServiceLocator.getLeaveCalendarDocumentHeaderService().getAllDocumentHeadersInRangeForPricipalId(principalId, startDate, endDate);
660 		
661 		BigDecimal previousFte = null;
662 		List<Job> jobs;
663 
664 		DateTime currentDate = startDate;
665 	    while (!currentDate.isAfter(endDate)) {
666 	    	RateRange rateRange = new RateRange();
667 	    	
668 	    	jobs = this.getJobsForDate(activeJobs, inactiveJobs, currentDate.toLocalDate());
669 	    	if(jobs.isEmpty()) {	// no jobs found for this day
670 	    		currentDate = currentDate.plusDays(1);
671 	    		continue;
672 	    	}
673 			rateRange.setJobs(jobs);
674 			
675 			// detect if there's a status change
676 			BigDecimal fteSum = HrServiceLocator.getJobService().getFteSumForJobs(jobs);
677 			rateRange.setAccrualRatePercentageModifier(fteSum);
678 			BigDecimal standardHours = HrServiceLocator.getJobService().getStandardHoursSumForJobs(jobs);
679 			rateRange.setStandardHours(standardHours);
680 			
681 			if(previousFte != null && !previousFte.equals(fteSum)) {
682 				rateRange.setStatusChanged(true);
683 				rrAggregate.setRateRangeChanged(true);
684 			}
685 			previousFte = fteSum;
686 			
687 			// figure out the PrincipalHRAttributes for this day
688 			PrincipalHRAttributes phra = this.getPrincipalHrAttributesForDate(phaList, currentDate.toLocalDate());
689 			rateRange.setPrincipalHRAttributes(phra);
690 			
691 			if(rateRange.getPrincipalHRAttributes() != null) {
692 				// figure out if there's an end principalHrAttributes for the initial principalHRAttributes
693 				PrincipalHRAttributes endPhra = this.getInactivePrincipalHrAttributesForDate(inactivePhaList, rateRange.getPrincipalHRAttributes().getEffectiveLocalDate(), currentDate.toLocalDate());
694 				rateRange.setEndPrincipalHRAttributes(endPhra);
695 			}
696 			
697 			// get leave plan for this day
698 			if(rateRange.getPrincipalHRAttributes()!= null) {				
699 				rateRange.setLeavePlan(this.getLeavePlanForDate(activeLpList, inactiveLpList, rateRange.getPrincipalHRAttributes().getLeavePlan(), currentDate.toLocalDate()));
700 			}
701 			
702 			if(rateRange.getLeavePlan() != null) {
703 				// get accrual category list for this day
704 				List<AccrualCategory> acsForDay = this.getAccrualCategoriesForDate(activeAccrCatList, inactiveAccrCatList, rateRange.getLeavePlan().getLeavePlan(), currentDate.toLocalDate());
705 				rateRange.setAcList(acsForDay);
706 				
707 				// get System scheduled time off for this day
708 				for(SystemScheduledTimeOffContract ssto : sstoList) {
709 					if(ssto.getAccruedLocalDate().equals(currentDate.toLocalDate())
710 							&& ssto.getLeavePlan().equals(rateRange.getLeavePlan().getLeavePlan())) {
711 						
712 						// figure out the primary leave assignment to use for ssto usage leave blocks
713 						if(CollectionUtils.isNotEmpty(jobs) && StringUtils.isBlank(rateRange.getPrimaryLeaveAssignmentId())) {
714 							for(Job aJob : jobs) {
715 								if(aJob.isEligibleForLeave() && aJob.isPrimaryJob()) {
716 									List<Assignment> assignmentList = HrServiceLocator.getAssignmentService().getActiveAssignmentsForJob(principalId, aJob.getJobNumber(), currentDate.toLocalDate());
717 									for(Assignment anAssignment : assignmentList) {
718 										if(anAssignment != null && anAssignment.isPrimaryAssign()) {
719 											rateRange.setPrimaryLeaveAssignmentId(anAssignment.getTkAssignmentId());
720 											break;
721 										}
722 									}
723 								}
724 							}
725 						}
726 						
727 						// 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
728 						// this logic depends on the deactivateOldAccruals() runs before buildRateRangeAggregate()
729 						// because deactivateOldAccruals() removes accrued ssto leave blocks unless they are banked/transferred
730 						List<LeaveBlock> sstoLbList = LmServiceLocator.getLeaveBlockService().getSSTOLeaveBlocks(principalId, ssto.getLmSystemScheduledTimeOffId(), ssto.getAccruedLocalDate());
731 						if(CollectionUtils.isEmpty(sstoLbList)) {
732 							rateRange.setSysScheTimeOff(ssto);
733 						}
734 					}	
735 				}
736 			}
737 			// set accrual category rules for the day
738 			if(CollectionUtils.isNotEmpty(rateRange.getAcList())) {
739 				List<AccrualCategoryRule> rulesForDay = new ArrayList<AccrualCategoryRule>();
740 				for(AccrualCategory ac : rateRange.getAcList()) {
741 					rulesForDay.addAll(this.getAccrualCategoryRulesForDate
742 											(activeRuleList, ac.getLmAccrualCategoryId(), currentDate.toLocalDate(), rateRange.getPrincipalHRAttributes().getServiceLocalDate()));
743 				}
744 				rateRange.setAcRuleList(rulesForDay);
745 		    	
746 			}
747 
748 			Interval range = new Interval(currentDate, currentDate.plusDays(1));
749 			rateRange.setRange(range);
750 			// assign leave document id to range if there is an existing leave doc for currentDate.
751 			// The doc Id will be assigned to leave blocks created at this rate range
752 			rateRange.setLeaveCalendarDocumentId(this.getLeaveDocumentForDate(lcDocList, currentDate));
753 			rateRangeList.add(rateRange);
754 			
755 			currentDate = currentDate.plusDays(1);
756 	    }
757 		rrAggregate.setRateRanges(rateRangeList);
758 		rrAggregate.setCurrentRate(null);
759 		return rrAggregate;
760 	}
761 	
762 	private String getLeaveDocumentForDate(List<LeaveCalendarDocumentHeader> lcDocList, DateTime currentDate) {
763 		for(LeaveCalendarDocumentHeader lcdh : lcDocList) {
764 			if(!lcdh.getBeginDateTime().isAfter(currentDate) && lcdh.getEndDateTime().isAfter(currentDate)) {
765 				return lcdh.getDocumentId();
766 			}
767 		}
768 		return "";
769 	}
770 		
771 	public List<Job> getJobsForDate(List<Job> activeJobs, List<Job> inactiveJobs, LocalDate currentDate) {
772 		List<Job> jobs = new ArrayList<Job>();
773     	for(Job aJob : activeJobs) {
774     		if(!aJob.getEffectiveLocalDate().isAfter(currentDate)) {
775     			jobs.add(aJob);
776     		}
777     	}
778     	if(CollectionUtils.isNotEmpty(jobs)) {
779 	    	List<Job> tempList = new ArrayList<Job>();
780 	    	tempList.addAll(jobs);
781 	    	for(Job aJob : tempList) {
782 	    		for(Job inactiveJob : inactiveJobs) {
783 	    			if(inactiveJob.getJobNumber().equals(aJob.getJobNumber())
784 	    				&& inactiveJob.getEffectiveLocalDate().isAfter(aJob.getEffectiveLocalDate())
785 	    				&& !inactiveJob.getEffectiveLocalDate().isAfter(currentDate)) {
786 	    					// remove inactive job from list
787 	    					jobs.remove(aJob);
788 	    			}
789 	    		}
790 	    	}
791     	}
792     	return jobs;
793 	}
794 	
795 	public PrincipalHRAttributes getPrincipalHrAttributesForDate(List<PrincipalHRAttributes> activeList, LocalDate currentDate) {
796 		List<PrincipalHRAttributes> phasForDay = new ArrayList<PrincipalHRAttributes>();
797 		for(PrincipalHRAttributes pha : activeList) {
798 			if(pha != null && pha.getEffectiveLocalDate() != null && pha.getServiceLocalDate() != null
799 					&& !pha.getEffectiveLocalDate().isAfter(currentDate) && !pha.getServiceLocalDate().isAfter(currentDate)) {
800     			phasForDay.add(pha);
801     		}
802 		}
803 		if(CollectionUtils.isNotEmpty(phasForDay)) {
804 			PrincipalHRAttributes pha = phasForDay.get(0);
805 			int indexOfMaxEffDt = 0;
806 			if(phasForDay.size() > 1) {
807 				for(int i = 1; i < phasForDay.size(); i++) {
808 					if( (phasForDay.get(i).getEffectiveLocalDate().isAfter(phasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()))
809 							||(phasForDay.get(i).getEffectiveLocalDate().equals(phasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate())
810 									&& phasForDay.get(i).getCreateTime().isAfter(phasForDay.get(indexOfMaxEffDt).getCreateTime()))) {
811 						indexOfMaxEffDt = i;
812 					}
813 				}
814 				pha = phasForDay.get(indexOfMaxEffDt);
815 			}
816 			return pha;
817 		}
818 		return null;
819 	}
820 	
821 	public PrincipalHRAttributes getInactivePrincipalHrAttributesForDate(List<PrincipalHRAttributes> inactiveList, LocalDate activeDate, LocalDate currentDate) {
822 		List<PrincipalHRAttributes> inactivePhasForDay = new ArrayList<PrincipalHRAttributes>();
823 		for(PrincipalHRAttributes pha : inactiveList) {
824 			if( pha.getEffectiveLocalDate().isAfter(activeDate) && !pha.getServiceLocalDate().isAfter(currentDate)) {
825 				inactivePhasForDay.add(pha);
826     		}
827 		}
828 		if(CollectionUtils.isNotEmpty(inactivePhasForDay)) {
829 			PrincipalHRAttributes pha = inactivePhasForDay.get(0);
830 			int indexOfMaxEffDt = 0;
831 			if(inactivePhasForDay.size() > 1) {
832 				for(int i = 1; i < inactivePhasForDay.size(); i++) {
833 					if( (inactivePhasForDay.get(i).getEffectiveLocalDate().isAfter(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()))
834 							||(inactivePhasForDay.get(i).getEffectiveLocalDate().equals(inactivePhasForDay.get(indexOfMaxEffDt).getEffectiveLocalDate())
835 									&& inactivePhasForDay.get(i).getCreateTime().isAfter(inactivePhasForDay.get(indexOfMaxEffDt).getCreateTime()))) {
836 						indexOfMaxEffDt = i;
837 					}
838 				}
839 				pha = inactivePhasForDay.get(indexOfMaxEffDt);
840 			}
841 			return pha;
842 		}
843 		return null;
844 	}
845 	
846 	public LeavePlan getLeavePlanForDate(List<LeavePlan> activeLpList, List<LeavePlan> inactiveLpList, String leavePlan, LocalDate currentDate) {
847 		List<LeavePlan> lpsForDay = new ArrayList<LeavePlan>();
848 		for(LeavePlan lp : activeLpList) {
849 			if(lp.getLeavePlan().equals(leavePlan) && !lp.getEffectiveLocalDate().isAfter(currentDate)) {
850 				lpsForDay.add(lp);
851 			}
852 		}
853 		List<LeavePlan> aList = new ArrayList<LeavePlan>();
854 		aList.addAll(lpsForDay);
855     	for(LeavePlan lp : aList) {
856     		for(LeavePlan inactiveLp : inactiveLpList) {
857     			if(inactiveLp.getLeavePlan().equals(lp.getLeavePlan())
858     				&& inactiveLp.getEffectiveLocalDate().isAfter(lp.getEffectiveLocalDate())
859     				&& !inactiveLp.getEffectiveLocalDate().isAfter(currentDate)) {
860     					// remove inactive leave plan from list
861     					lpsForDay.remove(lp);
862     			}
863     		}
864     	}
865 		if(CollectionUtils.isNotEmpty(lpsForDay)) {
866 			LeavePlan aLp = lpsForDay.get(0);
867 			int indexOfMaxEffDt = 0;
868 			if(lpsForDay.size() > 1) {
869 				for(int i = 1; i < lpsForDay.size(); i++) {
870 					if( (lpsForDay.get(i).getEffectiveLocalDate().isAfter(lpsForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()))
871 							||(lpsForDay.get(i).getEffectiveLocalDate().compareTo(lpsForDay.get(indexOfMaxEffDt).getEffectiveLocalDate()) == 0
872 									&& lpsForDay.get(i).getCreateTime().isAfter(lpsForDay.get(indexOfMaxEffDt).getCreateTime()))) {
873 						indexOfMaxEffDt = i;
874 					}
875 				}
876 				aLp = lpsForDay.get(indexOfMaxEffDt);
877 			}
878 			return aLp;
879 		}
880 		return null;
881 	}
882 	
883 	public List<AccrualCategory> getAccrualCategoriesForDate(List<AccrualCategory> activeAccrCatList, List<AccrualCategory> inactiveAccrCatList, String leavePlan, LocalDate currentDate) {
884 		Set<AccrualCategory> aSet = new HashSet<AccrualCategory>();
885 		for(AccrualCategory ac : activeAccrCatList) {
886 			if(ac.getLeavePlan().equals(leavePlan) && !ac.getEffectiveLocalDate().isAfter(currentDate)) {
887 				aSet.add(ac);
888 			}
889 		}
890 		List<AccrualCategory> list1 = new ArrayList<AccrualCategory>();
891 		list1.addAll(aSet);
892     	for(AccrualCategory ac : list1) {
893     		for(AccrualCategory inactiveAc : inactiveAccrCatList) {
894     			if(inactiveAc.getAccrualCategory().equals(ac.getAccrualCategory())
895     				&& inactiveAc.getEffectiveLocalDate().isAfter(ac.getEffectiveLocalDate())
896     				&& !inactiveAc.getEffectiveLocalDate().isAfter(currentDate)) {
897     					// remove inactive accrual category from list
898     				aSet.remove(ac);
899     			}
900     		}
901     	}
902     	List<AccrualCategory> acsForDay = new ArrayList<AccrualCategory>();
903     	acsForDay.addAll(aSet);
904     	return acsForDay;    	
905 	}
906 	
907 	@Override
908 	public boolean isEmpoyeementFutureStatusChanged(String principalId, DateTime startDate, DateTime endDate) {
909 		if(endDate.isAfter(LocalDate.now().toDateTimeAtStartOfDay())) {
910 			RateRangeAggregate rrAggregate = this.buildRateRangeAggregate(principalId, startDate, endDate);
911 			if(rrAggregate.isRateRangeChanged()) {
912 				return true;
913 			}
914 		}
915 		return false;
916 	}
917 	
918 	@Override
919 	public void calculateFutureAccrualUsingPlanningMonth(String principalId, LocalDate asOfDate, String runAsPrincipalId) {
920 		PrincipalHRAttributes phra = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, asOfDate);
921 		if(phra != null) {
922 			// use the date from pay period to get the leave plan
923 			LeavePlanContract lp = HrServiceLocator.getLeavePlanService().getLeavePlan(phra.getLeavePlan(), asOfDate);  
924 			if(lp != null && StringUtils.isNotEmpty(lp.getPlanningMonths())) {
925 				// go back a year 
926 				LocalDate startDate = asOfDate.minusYears(1);
927 				if(startDate.getDayOfMonth() > startDate.dayOfMonth().getMaximumValue()) {
928 					startDate = startDate.withDayOfMonth(startDate.dayOfMonth().getMaximumValue());
929 				}
930 				// go forward using planning months
931 				LocalDate endDate = asOfDate.plusMonths(Integer.parseInt(lp.getPlanningMonths()));
932 				// max days in months differ, if the date is bigger than the max day, set it to the max day of the month
933 				if(endDate.getDayOfMonth() > endDate.dayOfMonth().getMaximumValue()) {
934 					endDate = endDate.withDayOfMonth(endDate.dayOfMonth().getMaximumValue());
935 				}
936 				runAccrual(principalId, startDate.toDateTimeAtStartOfDay(), endDate.toDateTimeAtStartOfDay(), true, runAsPrincipalId);
937 
938 			}
939 		}
940 	}
941 	
942 	private boolean minimumPercentageReachedForPayPeriod(BigDecimal min, String earnInterval, int workDays, DateTime intervalDate, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
943 		if(min == null || min.compareTo(BigDecimal.ZERO) == 0) {
944 			return true;
945 		}
946 		int daysInInterval = this.getWorkDaysInInterval(intervalDate, earnInterval, payCalName, aMap);
947 		if(daysInInterval == 0) {
948 			return true;
949 		}
950 		BigDecimal actualPercentage =  new BigDecimal(workDays).divide(new BigDecimal(daysInInterval), 2, BigDecimal.ROUND_HALF_EVEN);
951 		if(actualPercentage.compareTo(min) >= 0) {
952 			return true;
953 		}
954 		
955 		return false;	
956 	}
957 
958 	private DateTime getPrevIntervalDate(DateTime aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
959 		if(earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
960 			return this.getPrevPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
961 		} else {
962 			return this.getPreviousAccrualIntervalDate(earnInterval, aDate);
963 		}
964 	}
965 	
966 	@Override
967 	public DateTime getPreviousAccrualIntervalDate(String earnInterval, DateTime aDate) {
968 		DateTime previousAccrualIntervalDate = null;
969         AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
970 		if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
971 			previousAccrualIntervalDate = aDate.minusDays(1);
972 		} else if(AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
973 			previousAccrualIntervalDate = aDate.minusWeeks(1).withDayOfWeek(DateTimeConstants.SATURDAY);
974 		} else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
975 			previousAccrualIntervalDate = aDate.minusDays(15);
976 			if (previousAccrualIntervalDate.getDayOfMonth() <= 15) {
977 				previousAccrualIntervalDate = previousAccrualIntervalDate.withDayOfMonth(15);
978 			} else {
979 				previousAccrualIntervalDate = previousAccrualIntervalDate.withDayOfMonth(previousAccrualIntervalDate.dayOfMonth().getMaximumValue());
980 			}
981 		} else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
982 			previousAccrualIntervalDate = aDate.minusMonths(1);
983 			previousAccrualIntervalDate = previousAccrualIntervalDate.withDayOfMonth(previousAccrualIntervalDate.dayOfMonth().getMaximumValue());
984 		} else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
985 			previousAccrualIntervalDate = aDate.minusYears(1);
986 			previousAccrualIntervalDate = previousAccrualIntervalDate.withDayOfYear(previousAccrualIntervalDate.dayOfYear().getMaximumValue());
987 		} else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
988 			previousAccrualIntervalDate = aDate;
989 		}
990 		
991 		return previousAccrualIntervalDate;
992 	}
993 	
994 	private DateTime getPrevPayCalIntervalDate(DateTime aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
995 		if(StringUtils.isNotEmpty(payCalName) 
996 				&& !aMap.isEmpty()
997 				&& earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {	// only used for ac earn interval == pay calendar
998 			List<CalendarEntry> entryList = aMap.get(payCalName);
999 			if(CollectionUtils.isNotEmpty(entryList)) {
1000 				for(CalendarEntry anEntry : entryList) {
1001 					// 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
1002 					DateTime endDate = anEntry.getEndPeriodFullDateTime().minusDays(1);
1003 					if(anEntry.getBeginPeriodFullDateTime().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
1004 						// the day before the beginning date of the cal entry that contains the passed in date is the endDate of previous calendar entry
1005 						DateTime prevIntvDate = anEntry.getBeginPeriodFullDateTime().minusDays(1);
1006 						return prevIntvDate;
1007 					}
1008 				}
1009 			}
1010 		}
1011 		return aDate;
1012 	}
1013 
1014     @Override
1015 	public DateTime getNextIntervalDate(DateTime aDate, String earnInterval, String payCalName, Map<String, List<CalendarEntry>> aMap) {
1016 		if(earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
1017 			return this.getNextPayCalIntervalDate(aDate, earnInterval, payCalName, aMap);
1018 		} else {
1019 			return this.getNextAccrualIntervalDate(earnInterval, aDate);
1020 		}
1021 	}
1022 	
1023 	private DateTime getNextPayCalIntervalDate(DateTime aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
1024 		if(StringUtils.isNotEmpty(payCalName) 
1025 				&& !aMap.isEmpty()
1026 				&& earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {	// only used for ac earn interval == pay calendar
1027 			List<CalendarEntry> entryList = aMap.get(payCalName);
1028 			if(CollectionUtils.isNotEmpty(entryList)) {
1029 				for(CalendarEntry anEntry : entryList) {
1030 					// 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
1031 					DateTime endDate = anEntry.getEndPeriodFullDateTime().minusDays(1);
1032 					if(anEntry.getBeginPeriodFullDateTime().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
1033 						// the endDate of the cal entry that contains the passed in date is the next pay calendar interval date
1034 						return endDate;
1035 					}
1036 				}
1037 			}
1038 		}
1039 		return aDate;
1040 	}
1041 	
1042 	@Override
1043 	public DateTime getNextAccrualIntervalDate(String earnInterval, DateTime aDate) {
1044 		DateTime nextAccrualIntervalDate = null;
1045         AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
1046 		if (AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
1047 			nextAccrualIntervalDate = aDate;
1048 		} else if(AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
1049 			if (aDate.getDayOfWeek() != DateTimeConstants.SATURDAY) {
1050 				nextAccrualIntervalDate = aDate.withDayOfWeek(DateTimeConstants.SATURDAY);
1051 			} else {
1052 				nextAccrualIntervalDate = aDate.withWeekOfWeekyear(1);
1053 			}
1054 		} else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
1055 			if(aDate.getDayOfMonth() <= 15) {
1056 				nextAccrualIntervalDate = aDate.withDayOfMonth(15);
1057 			} else {
1058 				nextAccrualIntervalDate = aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue());
1059 			}
1060 		} else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
1061 			nextAccrualIntervalDate = aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue());
1062 		} else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
1063 			nextAccrualIntervalDate = aDate.withDayOfYear(aDate.dayOfYear().getMaximumValue());
1064 		} else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
1065 			nextAccrualIntervalDate = aDate;
1066 		} else if (AccrualEarnInterval.PAY_CAL.equals(accrualEarnInterval)) {
1067 			LOG.error("Accrual Earn Interval of Pay CAL is not valid for AccrualServiceImpl:getNextAccrualIntervalDate");
1068             nextAccrualIntervalDate = null;
1069 		}
1070 		
1071 		return nextAccrualIntervalDate;
1072 	}
1073 
1074 	private int getWorkDaysInInterval(DateTime aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
1075 		if(earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {
1076 			return this.getWorkDaysInPayCalInterval(aDate, earnInterval, payCalName, aMap);
1077 		} else {
1078 			return this.getWorkDaysInAccrualInterval(earnInterval, aDate);
1079 		}
1080 	}
1081 	
1082 	private int getWorkDaysInPayCalInterval(DateTime aDate, String earnInterval, String payCalName,  Map<String, List<CalendarEntry>> aMap) {
1083 		if(StringUtils.isNotEmpty(payCalName) 
1084 				&& !aMap.isEmpty()
1085 				&& earnInterval.equals(AccrualEarnInterval.PAY_CAL.getCode())) {	// only used for ac earn interval == pay calendar
1086 			List<CalendarEntry> entryList = aMap.get(payCalName);
1087 			if(CollectionUtils.isNotEmpty(entryList)) {
1088 				for(CalendarEntry anEntry : entryList) {
1089 					// 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
1090 					DateTime endDate = anEntry.getEndPeriodFullDateTime().minusDays(1);
1091 					if(anEntry.getBeginPeriodFullDateTime().compareTo(aDate) <= 0 && endDate.compareTo(aDate) >= 0) {
1092 						return TKUtils.getWorkDays(anEntry.getBeginPeriodFullDateTime(), endDate);
1093 					}
1094 				}
1095 			}
1096 		}
1097 		return 0;
1098 	}
1099 	
1100 	@Override
1101 	public int getWorkDaysInAccrualInterval(String earnInterval, DateTime aDate) {
1102         AccrualEarnInterval accrualEarnInterval = AccrualEarnInterval.fromCode(earnInterval);
1103         if (accrualEarnInterval != null) {
1104             if(AccrualEarnInterval.DAILY.equals(accrualEarnInterval)) {
1105                 return 1;
1106             } else if(AccrualEarnInterval.WEEKLY.equals(accrualEarnInterval)) {
1107                 return 5;
1108             } else if (AccrualEarnInterval.SEMI_MONTHLY.equals(accrualEarnInterval)) {
1109                 if(aDate.getDayOfMonth() <= 15) {
1110                     return TKUtils.getWorkDays(aDate.withDayOfMonth(1), aDate.withDayOfMonth(15));
1111                 } else {
1112                     return TKUtils.getWorkDays(aDate.withDayOfMonth(16), aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue()));
1113                 }
1114             } else if (AccrualEarnInterval.MONTHLY.equals(accrualEarnInterval)) {
1115                 return TKUtils.getWorkDays(aDate.withDayOfMonth(1), aDate.withDayOfMonth(aDate.dayOfMonth().getMaximumValue()));
1116             } else if (AccrualEarnInterval.YEARLY.equals(accrualEarnInterval)) {
1117                 return TKUtils.getWorkDays(aDate.withDayOfYear(1), aDate.withDayOfYear(aDate.dayOfYear().getMaximumValue()));
1118             } else if (AccrualEarnInterval.NO_ACCRUAL.equals(accrualEarnInterval)) {
1119                 return 0;
1120             }
1121         }
1122 		return 0;
1123 	}
1124 	
1125 	public DateTime getRuleStartDate(String earnInterval, LocalDate serviceDate, Long startAcc) {
1126 		DateTime ruleStartDate = null;
1127 		
1128 		String intervalValue = HrConstants.SERVICE_UNIT_OF_TIME.get(earnInterval);
1129 
1130         switch (intervalValue) {
1131             case "Months":
1132                 ruleStartDate = serviceDate.toDateTimeAtStartOfDay().plusMonths(startAcc.intValue());
1133                 if (ruleStartDate.getDayOfMonth() > ruleStartDate.dayOfMonth().getMaximumValue()) {
1134                     ruleStartDate = ruleStartDate.withDayOfMonth(ruleStartDate.dayOfMonth().getMaximumValue());
1135                 }
1136                 break;
1137             case "Years":
1138                 ruleStartDate = serviceDate.toDateTimeAtStartOfDay().withYear(startAcc.intValue());
1139                 break;
1140             default:
1141                 ruleStartDate = serviceDate.toDateTimeAtStartOfDay();
1142                 break;
1143         }
1144 		
1145 		return ruleStartDate;
1146 	}
1147 	
1148 	public boolean isProrationFlag(String proration) {
1149 		if(proration == null) {
1150 			return true;
1151 		}
1152 		return proration.equals("Y") ? true : false;
1153 	}
1154 	
1155 	@Override
1156 	public boolean statusChangedSinceLastRun(String principalId) {
1157 		PrincipalAccrualRanContract par = LmServiceLocator.getPrincipalAccrualRanService().getLastPrincipalAccrualRan(principalId);
1158 		if(par == null) {
1159 			return true;
1160 		}
1161 		JobContract aJob = HrServiceLocator.getJobService().getMaxTimestampJob(principalId);
1162 		
1163 		if(aJob != null && aJob.getCreateTime().isAfter(new DateTime(par.getLastRanTs().getTime()))) {
1164 			return true;
1165 		}
1166 
1167         Assignment anAssign = HrServiceLocator.getAssignmentService().getMaxTimestampAssignment(principalId);
1168 		if(anAssign != null && anAssign.getCreateTime().isAfter(new DateTime(par.getLastRanTs().getTime()))) {
1169 			return true;
1170 		}
1171 		
1172 		PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getMaxTimeStampPrincipalHRAttributes(principalId);
1173 		if(pha != null && pha.getCreateTime().isAfter(par.getLastRanDateTime())) {
1174 			return true;
1175 		}
1176 		// if there are leave blocks created for earn codes with eligible-for-accrual = no since the last accrual run, it should trigger recalculation 
1177 		List<LeaveBlock> lbList = LmServiceLocator.getLeaveBlockService().getABELeaveBlocksSinceTime(principalId, par.getLastRanDateTime());
1178         return CollectionUtils.isNotEmpty(lbList);
1179     }
1180 	
1181     public List<AccrualCategoryRule> getAccrualCategoryRulesForDate(List<AccrualCategoryRule> acrList, String accrualCategoryId, LocalDate currentDate, LocalDate serviceDate) {
1182     	List<AccrualCategoryRule> aList = new ArrayList<AccrualCategoryRule>();
1183     	if(CollectionUtils.isNotEmpty(acrList)) {
1184 	    	for(AccrualCategoryRule acr : acrList) {
1185 	    		if(acr.getLmAccrualCategoryId().equals(accrualCategoryId)) {
1186 		    		String uot = acr.getServiceUnitOfTime();
1187 		    		int startTime = acr.getStart().intValue();
1188 					int endTime = acr.getEnd().intValue();
1189 					
1190 					LocalDate startDate = serviceDate;
1191 					LocalDate endDate = serviceDate;
1192 		    		if(uot.equals("M")) {		// monthly
1193 		    			startDate = startDate.plusMonths(startTime);
1194 		    			endDate = endDate.plusMonths(endTime).minusDays(1);
1195 		    		} else if(uot.endsWith("Y")) { // yearly
1196 		    			startDate = startDate.plusYears(startTime);
1197 		    			endDate = endDate.plusYears(endTime).minusDays(1);
1198 		    		}
1199 		    		
1200 		    		// max days in months differ, if the date is bigger than the max day, set it to the max day of the month
1201 					if(startDate.getDayOfMonth() > startDate.dayOfMonth().getMaximumValue()) {
1202 						startDate = startDate.withDayOfMonth(startDate.dayOfMonth().getMaximumValue());
1203 					}
1204 					if(endDate.getDayOfMonth() > endDate.dayOfMonth().getMaximumValue()) {
1205 						endDate = endDate.withDayOfMonth(endDate.dayOfMonth().getMaximumValue());
1206 					}
1207 		    		
1208 		    		if(currentDate.compareTo(startDate) >= 0 && currentDate.compareTo(endDate) <=0 ) {
1209 		    			aList.add(acr);
1210 		    		}
1211 	    		}
1212 	    	}
1213     	}
1214     	return aList;
1215 	}
1216     
1217     public AccrualCategoryRuleContract getRuleForAccrualCategory(List<AccrualCategoryRule> acrList, AccrualCategoryContract ac) {
1218     	if(CollectionUtils.isNotEmpty(acrList)) {
1219 	    	for(AccrualCategoryRuleContract acr : acrList) {
1220 	    		if(acr.getLmAccrualCategoryId().equals(ac.getLmAccrualCategoryId())) {
1221 	    			return acr;
1222 	    		}
1223 	    	}
1224 	    }
1225     	return null;
1226     }
1227     
1228     
1229     @Override
1230 	public BigDecimal getAccruedBalanceForPrincipal(String principalId,
1231 			AccrualCategoryContract accrualCategory, LocalDate asOfDate) {
1232     	BigDecimal balance = new BigDecimal(0);
1233     	PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, asOfDate);
1234     	if(pha == null)
1235     		return BigDecimal.ZERO;
1236     	
1237     	List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithAccrualCategory(principalId, pha.getServiceLocalDate(), asOfDate, accrualCategory.getAccrualCategory());
1238     	for(LeaveBlock block : leaveBlocks) {
1239     		if(!(StringUtils.equals(block.getRequestStatus(),HrConstants.REQUEST_STATUS.DEFERRED)
1240     				|| StringUtils.equals(block.getRequestStatus(),HrConstants.REQUEST_STATUS.DISAPPROVED))) {
1241     			balance = balance.add(block.getLeaveAmount());
1242 /*    			EarnCode code = accrualCategory.getEarnCodeObj();
1243     			if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Usage"))
1244     				balance = balance.subtract(block.getLeaveAmount().abs());
1245     			if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Adjustment"))
1246     				balance = balance.add(block.getLeaveAmount());*/
1247     		}
1248     	}
1249 		return balance;
1250 	}
1251 
1252 	@Override
1253 	public BigDecimal getApprovedBalanceForPrincipal(String principalId,
1254 			AccrualCategoryContract accrualCategory, LocalDate asOfDate) {
1255     	BigDecimal balance = new BigDecimal(0);
1256     	PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, asOfDate);
1257     	List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksWithAccrualCategory(principalId, pha.getServiceLocalDate(), asOfDate, accrualCategory.getAccrualCategory());
1258     	for(LeaveBlock block : leaveBlocks) {
1259     		if(StringUtils.equals(block.getRequestStatus(),HrConstants.REQUEST_STATUS.APPROVED)) {
1260 				balance = balance.add(block.getLeaveAmount());
1261 /*    			EarnCode code = accrualCategory.getEarnCodeObj();
1262     			if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Usage"))
1263     				balance = balance.subtract(block.getLeaveAmount().abs());
1264     			if(StringUtils.equals(HrConstants.ACCRUAL_BALANCE_ACTION_MAP.get(code.getAccrualBalanceAction()), "Adjustment"))
1265     				balance = balance.add(block.getLeaveAmount());*/
1266     		}
1267     	}
1268 		return balance;
1269 	}
1270 
1271 	@Override
1272 	public void runAccrualForLeavePlan(LeavePlanContract aLeavePlan, DateTime startDate, DateTime endDate, boolean recordRanData) {
1273 		if(aLeavePlan != null) {
1274 			List<PrincipalHRAttributes> phaList = HrServiceLocator.getPrincipalHRAttributeService().getActiveEmployeesForLeavePlan(aLeavePlan.getLeavePlan(), aLeavePlan.getEffectiveLocalDate());
1275 			for(PrincipalHRAttributes aPHA : phaList) {
1276 				String anId = aPHA.getPrincipalId();
1277 				if(LmServiceLocator.getLeaveAccrualService().statusChangedSinceLastRun(anId)) {
1278 					DateTime startDT = startDate == null ? getStartAccrualDate(anId) : startDate;
1279 					DateTime endDT = endDate == null ? getEndAccrualDate(anId) : endDate;	
1280 					if(startDT != null && endDT != null) {
1281 						this.runAccrual(anId, startDT, endDT, recordRanData);
1282 					}
1283 				}
1284 			}
1285 		}
1286 	}
1287 }