View Javadoc

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