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.hr.time.overtime.weekly.rule.service;
17  
18  import java.math.BigDecimal;
19  import java.sql.Date;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Comparator;
24  import java.util.List;
25  import java.util.ListIterator;
26  import java.util.Set;
27  
28  import org.apache.commons.collections.CollectionUtils;
29  import org.apache.commons.lang.ObjectUtils;
30  import org.apache.commons.lang.StringUtils;
31  import org.joda.time.DateTimeZone;
32  import org.kuali.hr.time.calendar.CalendarEntries;
33  import org.kuali.hr.time.earncode.EarnCode;
34  import org.kuali.hr.time.flsa.FlsaDay;
35  import org.kuali.hr.time.flsa.FlsaWeek;
36  import org.kuali.hr.time.overtime.weekly.rule.WeeklyOvertimeRule;
37  import org.kuali.hr.time.overtime.weekly.rule.dao.WeeklyOvertimeRuleDao;
38  import org.kuali.hr.time.service.base.TkServiceLocator;
39  import org.kuali.hr.time.timeblock.TimeBlock;
40  import org.kuali.hr.time.timeblock.TimeHourDetail;
41  import org.kuali.hr.time.timesheet.TimesheetDocument;
42  import org.kuali.hr.time.util.TKUtils;
43  import org.kuali.hr.time.util.TkConstants;
44  import org.kuali.hr.time.util.TkTimeBlockAggregate;
45  import org.kuali.hr.time.workarea.WorkArea;
46  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
47  import org.kuali.rice.krad.service.KRADServiceLocator;
48  
49  public class WeeklyOvertimeRuleServiceImpl implements WeeklyOvertimeRuleService {
50  
51  	private WeeklyOvertimeRuleDao weeklyOvertimeRuleDao;
52  
53  	@Override
54  	public void processWeeklyOvertimeRule(TimesheetDocument timesheetDocument, TkTimeBlockAggregate aggregate) {
55  		Date asOfDate = TKUtils.getTimelessDate(timesheetDocument.getDocumentHeader().getEndDate());
56  		String principalId = timesheetDocument.getDocumentHeader().getPrincipalId();
57  		java.util.Date beginDate = timesheetDocument.getDocumentHeader().getBeginDate();
58  		java.util.Date endDate = timesheetDocument.getDocumentHeader().getEndDate();
59  		List<WeeklyOvertimeRule> weeklyOvertimeRules = getWeeklyOvertimeRules(asOfDate);
60  
61  		List<List<FlsaWeek>> flsaWeeks = getFlsaWeeks(principalId, beginDate, endDate, aggregate);
62  
63  		for (WeeklyOvertimeRule weeklyOvertimeRule : weeklyOvertimeRules) {
64  			Set<String> maxHoursEarnCodes = TkServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(weeklyOvertimeRule.getMaxHoursEarnGroup(), asOfDate);
65  			Set<String> convertFromEarnCodes = TkServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(weeklyOvertimeRule.getConvertFromEarnGroup(), asOfDate);
66  
67  			for (List<FlsaWeek> flsaWeekParts : flsaWeeks) {
68  				BigDecimal existingMaxHours = getMaxHours(flsaWeekParts, maxHoursEarnCodes);
69  				BigDecimal newOvertimeHours = existingMaxHours.subtract(weeklyOvertimeRule.getMaxHours(), TkConstants.MATH_CONTEXT);
70  				
71  				if (newOvertimeHours.compareTo(BigDecimal.ZERO) != 0) {
72  					applyOvertimeToFlsaWeeks(flsaWeekParts, weeklyOvertimeRule, asOfDate, convertFromEarnCodes, newOvertimeHours);
73  				}
74  			}
75  		}
76  		
77  		savePreviousNextCalendarTimeBlocks(flsaWeeks);
78  	}
79  	
80  	/**
81  	 * Get the list of all FlsaWeek lists for this period.  An FlsaWeek list is a set of partial FlsaWeeks that overlap a CalendarEntry boundary.  The total of
82  	 * all days in an FlsaWeek list adds up to one complete week.  This is done so WeeklyOvertimeRule calculations are done for multiple Calendar periods.
83  	 * 
84  	 * @param principalId The principal id to apply the rules to
85  	 * @param beginDate The begin date of the CalendarEntry
86  	 * @param endDate The end date of the CalendarEntry
87  	 * @param aggregate The aggregate of the applicable TimeBlocks
88  	 * 
89  	 * @return the list of all FlsaWeek lists for this period
90  	 */
91  	protected List<List<FlsaWeek>> getFlsaWeeks(String principalId, java.util.Date beginDate, java.util.Date endDate, TkTimeBlockAggregate aggregate) {
92  		List<List<FlsaWeek>> flsaWeeks = new ArrayList<List<FlsaWeek>>();
93  		
94          DateTimeZone zone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
95  		List<FlsaWeek> currentWeeks = aggregate.getFlsaWeeks(zone);
96  		
97  		for (ListIterator<FlsaWeek> weekIterator = currentWeeks.listIterator(); weekIterator.hasNext(); ) {
98  			List<FlsaWeek> flsaWeek = new ArrayList<FlsaWeek>();
99  			
100 			int index = weekIterator.nextIndex();
101 			FlsaWeek currentWeek = weekIterator.next();
102 			
103 			if (index == 0 && !currentWeek.isFirstWeekFull()) {
104 				TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, beginDate);
105 				if (timesheetDocumentHeader != null) { 
106 					List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
107 					if (CollectionUtils.isNotEmpty(timeBlocks)) {
108 						CalendarEntries calendarEntry = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(principalId, timesheetDocumentHeader.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
109 						TkTimeBlockAggregate previousAggregate = new TkTimeBlockAggregate(timeBlocks, calendarEntry, calendarEntry.getCalendarObj(), true);
110 						List<FlsaWeek> previousWeek = previousAggregate.getFlsaWeeks(zone);
111 						if (CollectionUtils.isNotEmpty(previousWeek)) {
112 							flsaWeek.add(previousWeek.get(previousWeek.size() - 1));
113 						}
114 					}
115 				 }
116 			}
117 			
118 			flsaWeek.add(currentWeek);
119 			
120 			if (index == currentWeeks.size() - 1 && !currentWeek.isLastWeekFull()) {
121 				TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getNextDocumentHeader(principalId, endDate);
122 				if (timesheetDocumentHeader != null) { 
123 					List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
124 					if (CollectionUtils.isNotEmpty(timeBlocks)) {
125 						CalendarEntries calendarEntry = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(principalId, timesheetDocumentHeader.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
126 						TkTimeBlockAggregate nextAggregate = new TkTimeBlockAggregate(timeBlocks, calendarEntry, calendarEntry.getCalendarObj(), true);
127 						List<FlsaWeek> nextWeek = nextAggregate.getFlsaWeeks(zone);
128 						if (CollectionUtils.isNotEmpty(nextWeek)) {
129 							flsaWeek.add(nextWeek.get(0));
130 						}
131 					}
132 				 }
133 			}
134 			
135 			flsaWeeks.add(flsaWeek);
136 		}
137 		
138 		return flsaWeeks;
139 	}
140 	
141 	/**
142 	 * Get the maximum worked hours for the given FlsaWeeks.
143 	 * 
144 	 * @param flsaWeeks The FlsaWeeks to get the hours from
145 	 * @param maxHoursEarnCodes The EarnCodes used to determine what is applicable as a max hour
146 	 * 
147 	 * @return the maximum worked hours for the given FlsaWeeks
148 	 */
149 	protected BigDecimal getMaxHours(List<FlsaWeek> flsaWeeks, Set<String> maxHoursEarnCodes) {
150 		BigDecimal maxHours = BigDecimal.ZERO;
151 		
152 		for (FlsaWeek flsaWeek : flsaWeeks) {
153 			for (FlsaDay flsaDay : flsaWeek.getFlsaDays()) {
154 				for (TimeBlock timeBlock : flsaDay.getAppliedTimeBlocks()) {
155 					for (TimeHourDetail timeHourDetail : timeBlock.getTimeHourDetails()) {
156 						if (maxHoursEarnCodes.contains(timeHourDetail.getEarnCode())) {
157 							maxHours = maxHours.add(timeHourDetail.getHours(), TkConstants.MATH_CONTEXT);
158 						}
159 					}
160 				}
161 			}
162 		}
163 		
164 		return maxHours;
165 	}
166 	
167 	/**
168 	 * Returns the overtime EarnCode.  Defaults to the EarnCode on the WeeklyOvertimeRule but overrides with the WorkArea default overtime EarnCode and 
169 	 * the TimeBlock overtime preference EarnCode in that order if they exist.
170 	 * 
171 	 * @param weeklyOvertimeRule The WeeklyOvertimeRule to use when calculating the overtime EarnCode
172 	 * @param timeBlock The TimeBlock to use when calculating the overtime EarnCode
173 	 * @param asOfDate The effectiveDate of the WorkArea
174 	 * 
175 	 * @return the overtime EarnCode
176 	 */
177 	protected String getOvertimeEarnCode(WeeklyOvertimeRule weeklyOvertimeRule, TimeBlock timeBlock, Date asOfDate) {
178         String overtimeEarnCode = weeklyOvertimeRule.getConvertToEarnCode();
179         
180         WorkArea workArea = TkServiceLocator.getWorkAreaService().getWorkArea(timeBlock.getWorkArea(), asOfDate);
181 		if (StringUtils.isNotBlank(workArea.getDefaultOvertimeEarnCode())){
182 			overtimeEarnCode = workArea.getDefaultOvertimeEarnCode();
183 		}
184 		
185         if (StringUtils.isNotEmpty(timeBlock.getOvertimePref())) {
186         	overtimeEarnCode = timeBlock.getOvertimePref();
187         }
188         
189         return overtimeEarnCode;
190 	}
191 	
192 	/**
193 	 * Applies overtime to the given FlsaWeeks.
194 	 * 
195 	 * @param flsaWeeks The FlsaWeeks to apply the overtime to
196 	 * @param weeklyOvertimeRule The WeeklyOvertimeRule in effective
197 	 * @param asOfDate The effectiveDate of the WorkArea
198 	 * @param convertFromEarnCodes The EarnCodes to convert to overtime
199 	 * @param overtimeHours The number of overtime hours to apply
200 	 */
201 	protected void applyOvertimeToFlsaWeeks(List<FlsaWeek> flsaWeeks, WeeklyOvertimeRule weeklyOvertimeRule, Date asOfDate, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
202 		List<FlsaDay> flsaDays = getFlsaDays(flsaWeeks);
203 		
204 		if (overtimeHours.compareTo(BigDecimal.ZERO) > 0) {
205 			applyPositiveOvertimeToFlsaWeek(flsaDays, weeklyOvertimeRule, asOfDate, convertFromEarnCodes, overtimeHours);
206 		} else if (overtimeHours.compareTo(BigDecimal.ZERO) < 0) {
207 			applyNegativeOvertimeToFlsaWeek(flsaDays, weeklyOvertimeRule, asOfDate, convertFromEarnCodes, overtimeHours);
208 		}
209 		
210 		removeEmptyOvertime(flsaDays, weeklyOvertimeRule, asOfDate);
211 	}
212 	
213 	/**
214 	 * Gets the list of FlsaDays in the given FlsaWeek.
215 	 * 
216 	 * @param flsaWeeks The FlsaWeek to fetch the FlsaDays from
217 	 * 
218 	 * @return the list of FlsaDays in the given FlsaWeek
219 	 */
220 	protected List<FlsaDay> getFlsaDays(List<FlsaWeek> flsaWeeks) {
221 		List<FlsaDay> flsaDays = new ArrayList<FlsaDay>();
222 		
223 		for (FlsaWeek flsaWeek : flsaWeeks) {
224 			flsaDays.addAll(flsaWeek.getFlsaDays());
225 		}
226 		
227 		return flsaDays;
228 	}
229 	
230 	/**
231 	 * Applies positive overtime to the given FlsaDays.
232 	 * 
233 	 * @param flsaDays The FlsaDays to apply the overtime to
234 	 * @param weeklyOvertimeRule The WeeklyOvertimeRule in effective
235 	 * @param asOfDate The effectiveDate of the WorkArea
236 	 * @param convertFromEarnCodes The EarnCodes to convert to overtime
237 	 * @param overtimeHours The number of overtime hours to apply
238 	 */
239 	protected void applyPositiveOvertimeToFlsaWeek(List<FlsaDay> flsaDays, WeeklyOvertimeRule weeklyOvertimeRule, Date asOfDate, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
240 		for (ListIterator<FlsaDay> dayIterator = flsaDays.listIterator(flsaDays.size()); dayIterator.hasPrevious(); ) {
241 			FlsaDay flsaDay = dayIterator.previous();
242 			
243 			List<TimeBlock> timeBlocks = flsaDay.getAppliedTimeBlocks();
244 			Collections.sort(timeBlocks, new Comparator<TimeBlock>() {
245 				public int compare(TimeBlock timeBlock1, TimeBlock timeBlock2) {
246 					return ObjectUtils.compare(timeBlock1.getBeginTimestamp(), timeBlock2.getBeginTimestamp());
247 				}
248 			});
249 
250 			for (ListIterator<TimeBlock> timeBlockIterator = timeBlocks.listIterator(timeBlocks.size()); timeBlockIterator.hasPrevious(); ) {
251 				TimeBlock timeBlock = timeBlockIterator.previous();
252 				String overtimeEarnCode = getOvertimeEarnCode(weeklyOvertimeRule, timeBlock, asOfDate);
253 				overtimeHours = applyPositiveOvertimeOnTimeBlock(timeBlock, overtimeEarnCode, convertFromEarnCodes, overtimeHours);
254 			}
255 			
256 			flsaDay.remapTimeHourDetails();
257 		}
258 	}
259 	
260 	/**
261 	 * Applies negative overtime to the given FlsaDays.
262 	 * 
263 	 * @param flsaDays The FlsaDays to apply the overtime to
264 	 * @param weeklyOvertimeRule The WeeklyOvertimeRule in effective
265 	 * @param asOfDate The effectiveDate of the WorkArea
266 	 * @param convertFromEarnCodes The EarnCodes to convert to overtime
267 	 * @param overtimeHours The number of overtime hours to apply
268 	 */
269 	protected void applyNegativeOvertimeToFlsaWeek(List<FlsaDay> flsaDays, WeeklyOvertimeRule weeklyOvertimeRule, Date asOfDate, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
270 		for (ListIterator<FlsaDay> dayIterator = flsaDays.listIterator(); dayIterator.hasNext(); ) {
271 			FlsaDay flsaDay = dayIterator.next();
272 			
273 			List<TimeBlock> timeBlocks = flsaDay.getAppliedTimeBlocks();
274 			Collections.sort(timeBlocks, new Comparator<TimeBlock>() {
275 				public int compare(TimeBlock timeBlock1, TimeBlock timeBlock2) {
276 					return ObjectUtils.compare(timeBlock1.getBeginTimestamp(), timeBlock2.getBeginTimestamp());
277 				}
278 			});
279 
280 			for (ListIterator<TimeBlock> timeBlockIterator = timeBlocks.listIterator(); timeBlockIterator.hasNext(); ) {
281 				TimeBlock timeBlock = timeBlockIterator.next();
282 				String overtimeEarnCode = getOvertimeEarnCode(weeklyOvertimeRule, timeBlock, asOfDate);
283 				overtimeHours = applyNegativeOvertimeOnTimeBlock(timeBlock, overtimeEarnCode, convertFromEarnCodes, overtimeHours);
284 			}
285 			
286 			flsaDay.remapTimeHourDetails();
287 		}
288 	}
289 	
290 	protected void removeEmptyOvertime(List<FlsaDay> flsaDays, WeeklyOvertimeRule weeklyOvertimeRule, Date asOfDate) {
291 		for (ListIterator<FlsaDay> dayIterator = flsaDays.listIterator(); dayIterator.hasNext(); ) {
292 			FlsaDay flsaDay = dayIterator.next();
293 			
294 			List<TimeBlock> timeBlocks = flsaDay.getAppliedTimeBlocks();
295 			for (ListIterator<TimeBlock> timeBlockIterator = timeBlocks.listIterator(); timeBlockIterator.hasNext(); ) {
296 				TimeBlock timeBlock = timeBlockIterator.next();
297 				String overtimeEarnCode = getOvertimeEarnCode(weeklyOvertimeRule, timeBlock, asOfDate);
298 
299 				List<TimeHourDetail> timeHourDetails = timeBlock.getTimeHourDetails();
300 				List<TimeHourDetail> oldTimeHourDetails = new ArrayList<TimeHourDetail>();
301 
302 				TimeHourDetail overtimeTimeHourDetail = getTimeHourDetailByEarnCode(timeHourDetails, Collections.singletonList(overtimeEarnCode));
303 				if (overtimeTimeHourDetail != null) {
304 					if (overtimeTimeHourDetail.getHours().compareTo(BigDecimal.ZERO) == 0) {
305 						oldTimeHourDetails.add(overtimeTimeHourDetail);
306 					}
307 				}
308 				
309 				for (TimeHourDetail oldTimeHourDetail : oldTimeHourDetails) {
310 					timeBlock.removeTimeHourDetail(oldTimeHourDetail);
311 				}
312 			}
313 		}
314 	}
315 
316 	/**
317 	 * Applies overtime additions to the indicated TimeBlock.
318 	 *
319 	 * @param timeBlock The time block to apply the overtime on.
320 	 * @param overtimeEarnCode The overtime earn code to apply overtime to.
321 	 * @param convertFromEarnCodes The other earn codes on the time block.
322 	 * @param overtimeHours The overtime hours to apply.
323 	 *
324 	 * @return the amount of overtime hours remaining to be applied.
325 	 */
326 	protected BigDecimal applyPositiveOvertimeOnTimeBlock(TimeBlock timeBlock, String overtimeEarnCode, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
327 		BigDecimal applied = BigDecimal.ZERO;
328 		List<TimeHourDetail> timeHourDetails = timeBlock.getTimeHourDetails();
329 		List<TimeHourDetail> newTimeHourDetails = new ArrayList<TimeHourDetail>();
330 		
331 		for (TimeHourDetail timeHourDetail : timeHourDetails) {
332 			if (convertFromEarnCodes.contains(timeHourDetail.getEarnCode())) {
333 				if (timeHourDetail.getHours().compareTo(overtimeHours) >= 0) {
334 					applied = overtimeHours;
335 				} else {
336 					applied = timeHourDetail.getHours();
337 				}
338 				
339 				EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(overtimeEarnCode, timeBlock.getEndDate());
340 				BigDecimal hours = earnCodeObj.getInflateFactor().multiply(applied, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_UP);
341 				
342 				TimeHourDetail overtimeTimeHourDetail = getTimeHourDetailByEarnCode(timeHourDetails, Collections.singletonList(overtimeEarnCode));
343 				if (overtimeTimeHourDetail != null) {
344 					overtimeTimeHourDetail.setHours(overtimeTimeHourDetail.getHours().add(hours, TkConstants.MATH_CONTEXT));
345 				} else {
346 					TimeHourDetail newTimeHourDetail = new TimeHourDetail();
347 					newTimeHourDetail.setTkTimeBlockId(timeBlock.getTkTimeBlockId());
348 					newTimeHourDetail.setEarnCode(overtimeEarnCode);
349 					newTimeHourDetail.setHours(hours);
350 		
351 					newTimeHourDetails.add(newTimeHourDetail);
352 				}
353 				
354 				timeHourDetail.setHours(timeHourDetail.getHours().subtract(applied, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_UP));
355 			}
356 		}
357 		
358 		for (TimeHourDetail newTimeHourDetail : newTimeHourDetails) {
359 			timeBlock.addTimeHourDetail(newTimeHourDetail);
360 		}
361 		
362 		return overtimeHours.subtract(applied);
363 	}
364 	
365 	/**
366 	 * Applies overtime subtractions on the indicated TimeBlock.
367 	 *
368 	 * @param timeBlock The time block to reset the overtime on.
369 	 * @param overtimeEarnCode The overtime earn code to apply overtime to.
370 	 * @param convertFromEarnCodes The other earn codes on the time block.
371 	 * @param overtimeHours The overtime hours to apply.
372 	 *
373 	 * @return the amount of overtime hours remaining to be applied.
374 	 */
375 	protected BigDecimal applyNegativeOvertimeOnTimeBlock(TimeBlock timeBlock, String overtimeEarnCode, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
376 		BigDecimal applied = BigDecimal.ZERO;
377 		List<TimeHourDetail> timeHourDetails = timeBlock.getTimeHourDetails();
378 		
379 		for (TimeHourDetail timeHourDetail : timeHourDetails) {
380 			if (convertFromEarnCodes.contains(timeHourDetail.getEarnCode())) {
381 				TimeHourDetail overtimeTimeHourDetail = getTimeHourDetailByEarnCode(timeHourDetails, Collections.singletonList(overtimeEarnCode));
382 			
383 				if (overtimeTimeHourDetail != null) {
384 					applied = overtimeTimeHourDetail.getHours().add(overtimeHours, TkConstants.MATH_CONTEXT);
385 					if (applied.compareTo(BigDecimal.ZERO) >= 0) {
386 						applied = overtimeHours;
387 					} else {
388 						applied = overtimeTimeHourDetail.getHours().negate();
389 					}
390 					
391 					EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(overtimeEarnCode, timeBlock.getEndDate());
392 					BigDecimal hours = earnCodeObj.getInflateFactor().multiply(applied, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_DOWN);
393 					
394 					overtimeTimeHourDetail.setHours(overtimeTimeHourDetail.getHours().add(hours, TkConstants.MATH_CONTEXT));
395 					
396 					timeHourDetail.setHours(timeHourDetail.getHours().subtract(applied, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_UP));
397 				}
398 			}
399 		}
400 		
401 		return overtimeHours.subtract(applied);
402 	}
403 	
404 	protected TimeHourDetail getTimeHourDetailByEarnCode(List<TimeHourDetail> timeHourDetails, Collection<String> earnCodes) {
405 		TimeHourDetail result = null;
406 		
407 		for (TimeHourDetail timeHourDetail : timeHourDetails) {
408 			if (earnCodes.contains(timeHourDetail.getEarnCode())) {
409 				result = timeHourDetail;
410 				break;
411 			}
412 		}
413 		
414 		return result;
415 	}
416 	
417 	/**
418 	 * Saves the TimeBlocks from both the previous and next CalendarEntries, since these are not saved automatically by calling methods.
419 	 * 
420 	 * @param flsaWeeks The list of FlsaWeek lists
421 	 */
422 	protected void savePreviousNextCalendarTimeBlocks(List<List<FlsaWeek>> flsaWeeks) {
423 		for (ListIterator<List<FlsaWeek>> weeksIterator = flsaWeeks.listIterator(); weeksIterator.hasNext(); ) {
424 			int index = weeksIterator.nextIndex();
425 			List<FlsaWeek> currentWeekParts = weeksIterator.next();
426 			
427 			if (index == 0 && currentWeekParts.size() > 1) {
428 				FlsaWeek previousFlsaWeek = currentWeekParts.get(0);
429 				for (FlsaDay flsaDay : previousFlsaWeek.getFlsaDays()) {
430 					KRADServiceLocator.getBusinessObjectService().save(flsaDay.getAppliedTimeBlocks());
431 				}
432 			}
433 				
434 			if (index == flsaWeeks.size() - 1 && currentWeekParts.size() > 1) {
435 				FlsaWeek nextFlsaWeek = currentWeekParts.get(currentWeekParts.size() - 1);
436 				for (FlsaDay flsaDay : nextFlsaWeek.getFlsaDays()) {
437 					KRADServiceLocator.getBusinessObjectService().save(flsaDay.getAppliedTimeBlocks());
438 				}
439 			}
440 		}
441 	}
442 
443 	@Override
444 	public List<WeeklyOvertimeRule> getWeeklyOvertimeRules(Date asOfDate) {
445 		return weeklyOvertimeRuleDao.findWeeklyOvertimeRules(asOfDate);
446 	}
447 
448 	@Override
449 	public void saveOrUpdate(WeeklyOvertimeRule weeklyOvertimeRule) {
450 		weeklyOvertimeRuleDao.saveOrUpdate(weeklyOvertimeRule);
451 	}
452 
453 	@Override
454 	public void saveOrUpdate(List<WeeklyOvertimeRule> weeklyOvertimeRules) {
455 		weeklyOvertimeRuleDao.saveOrUpdate(weeklyOvertimeRules);
456 	}
457 
458 	public void setWeeklyOvertimeRuleDao(WeeklyOvertimeRuleDao weeklyOvertimeRuleDao) {
459 		this.weeklyOvertimeRuleDao = weeklyOvertimeRuleDao;
460 	}
461 
462 	@Override
463 	public WeeklyOvertimeRule getWeeklyOvertimeRule(String tkWeeklyOvertimeRuleId) {
464 		return weeklyOvertimeRuleDao.getWeeklyOvertimeRule(tkWeeklyOvertimeRuleId);
465 	}
466 
467 }