1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package org.kuali.hr.time.shiftdiff.rule.service;
17  
18  import org.apache.log4j.Logger;
19  import org.joda.time.*;
20  import org.kuali.hr.job.Job;
21  import org.kuali.hr.time.calendar.CalendarEntries;
22  import org.kuali.hr.time.principal.PrincipalHRAttributes;
23  import org.kuali.hr.time.service.base.TkServiceLocator;
24  import org.kuali.hr.time.shiftdiff.rule.ShiftDifferentialRule;
25  import org.kuali.hr.time.shiftdiff.rule.dao.ShiftDifferentialRuleDao;
26  import org.kuali.hr.time.timeblock.TimeBlock;
27  import org.kuali.hr.time.timeblock.TimeHourDetail;
28  import org.kuali.hr.time.timesheet.TimesheetDocument;
29  import org.kuali.hr.time.util.TKUtils;
30  import org.kuali.hr.time.util.TkConstants;
31  import org.kuali.hr.time.util.TkTimeBlockAggregate;
32  import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
33  
34  import java.math.BigDecimal;
35  import java.sql.Date;
36  import java.util.*;
37  
38  
39  public class ShiftDifferentialRuleServiceImpl implements ShiftDifferentialRuleService {
40  
41  	@SuppressWarnings("unused")
42  	private static final Logger LOG = Logger.getLogger(ShiftDifferentialRuleServiceImpl.class);
43  	
44  
45  
46  
47  	private ShiftDifferentialRuleDao shiftDifferentialRuleDao = null;
48  
49  	private Map<Long,List<ShiftDifferentialRule>> getJobNumberToShiftRuleMap(TimesheetDocument timesheetDocument) {
50  		Map<Long,List<ShiftDifferentialRule>> jobNumberToShifts = new HashMap<Long,List<ShiftDifferentialRule>>();
51  		PrincipalHRAttributes principalCal = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(timesheetDocument.getPrincipalId(),timesheetDocument.getCalendarEntry().getEndPeriodDate());
52  
53  		for (Job job : timesheetDocument.getJobs()) {
54  			List<ShiftDifferentialRule> shiftDifferentialRules = getShiftDifferentalRules(job.getLocation(),job.getHrSalGroup(),job.getPayGrade(),principalCal.getPayCalendar(),
55  					TKUtils.getTimelessDate(timesheetDocument.getCalendarEntry().getEndPeriodDateTime()));
56  			if (shiftDifferentialRules.size() > 0)
57  				jobNumberToShifts.put(job.getJobNumber(), shiftDifferentialRules);
58  		}
59  
60  		return jobNumberToShifts;
61  	}
62  
63  	private Map<Long,List<TimeBlock>> getPreviousPayPeriodLastDayJobToTimeBlockMap(TimesheetDocument timesheetDocument, Map<Long,List<ShiftDifferentialRule>> jobNumberToShifts) {
64  		Map<Long, List<TimeBlock>> jobNumberToTimeBlocksPreviousDay = null;
65  
66  		
67  		
68  		List<TimeBlock> prevBlocks = TkServiceLocator.getTimesheetService().getPrevDocumentTimeBlocks(timesheetDocument.getPrincipalId(), timesheetDocument.getDocumentHeader().getBeginDate());
69  		if (prevBlocks.size() > 0) {
70  			TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(timesheetDocument.getPrincipalId(), timesheetDocument.getDocumentHeader().getBeginDate());
71  			if (prevTdh != null) {
72  				CalendarEntries prevPayCalendarEntry = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(timesheetDocument.getPrincipalId(), prevTdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
73  				TkTimeBlockAggregate prevTimeAggregate = new TkTimeBlockAggregate(prevBlocks, prevPayCalendarEntry, prevPayCalendarEntry.getCalendarObj(), true);
74  				List<List<TimeBlock>> dayBlocks = prevTimeAggregate.getDayTimeBlockList();
75  				List<TimeBlock> previousPeriodLastDayBlocks = dayBlocks.get(dayBlocks.size() - 1);
76  				
77  				if (previousPeriodLastDayBlocks.size() > 0) {
78  					jobNumberToTimeBlocksPreviousDay = new HashMap<Long, List<TimeBlock>>();
79  
80  					for (TimeBlock block : previousPeriodLastDayBlocks) {
81  						
82  						
83  						Long jobNumber = block.getJobNumber();
84  						if (jobNumberToShifts.containsKey(jobNumber)) {
85  							
86  							List<TimeBlock> jblist = jobNumberToTimeBlocksPreviousDay.get(jobNumber);
87  							if (jblist == null) {
88  								jblist = new ArrayList<TimeBlock>();
89  								jobNumberToTimeBlocksPreviousDay.put(jobNumber, jblist);
90  							}
91  							jblist.add(block);
92  						}
93  					}
94  				}
95  			}
96  		}
97  
98  		return jobNumberToTimeBlocksPreviousDay;
99  	}
100 
101 	private boolean timeBlockHasEarnCode(Set<String> earnCodes, TimeBlock block) {
102 		boolean present = false;
103 
104 		if (block != null && earnCodes != null)
105 			present = earnCodes.contains(block.getEarnCode());
106 
107 		return present;
108 	}
109 
110     
111 
112 
113 
114 
115 
116 
117 
118 
119     private BigDecimal negativeTimeHourDetailSum(TimeBlock block) {
120         BigDecimal sum = BigDecimal.ZERO;
121 
122         if (block != null) {
123             List<TimeHourDetail> details = block.getTimeHourDetails();
124             for (TimeHourDetail detail : details) {
125                 if (detail.getEarnCode().equals(TkConstants.LUNCH_EARN_CODE)) {
126                     sum = sum.add(detail.getHours());
127                 }
128             }
129         }
130 
131         return sum;
132     }
133 
134 	@Override
135 	public void processShiftDifferentialRules(TimesheetDocument timesheetDocument, TkTimeBlockAggregate aggregate) {
136         DateTimeZone zone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
137 		List<List<TimeBlock>> blockDays = aggregate.getDayTimeBlockList();
138 		DateTime periodStartDateTime = timesheetDocument.getCalendarEntry().getBeginLocalDateTime().toDateTime(zone);
139 		Map<Long,List<ShiftDifferentialRule>> jobNumberToShifts = getJobNumberToShiftRuleMap(timesheetDocument);
140 
141 
142         
143 		if (jobNumberToShifts.isEmpty()) {
144 			return;
145 		}
146 
147 		
148 		
149 		
150 		
151         
152         boolean previousPayPeriodPrevDay = true;
153 		Map<Long, List<TimeBlock>> jobNumberToTimeBlocksPreviousDay =
154                 getPreviousPayPeriodLastDayJobToTimeBlockMap(timesheetDocument, jobNumberToShifts);
155 
156 		
157         
158         
159 		for (int pos = 0; pos < blockDays.size(); pos++) {
160 			List<TimeBlock> blocks = blockDays.get(pos); 
161 			if (blocks.isEmpty())
162 				continue; 
163 
164 			DateTime currentDay = periodStartDateTime.plusDays(pos);
165 			Interval virtualDay = new Interval(currentDay, currentDay.plusHours(24));
166 
167 			
168             
169             
170             
171             
172             
173 			Map<Long, List<TimeBlock>> jobNumberToTimeBlocks = new HashMap<Long,List<TimeBlock>>();
174 			for (TimeBlock block : blocks) {
175 				Long jobNumber = block.getJobNumber();
176 				if (jobNumberToShifts.containsKey(jobNumber)) {
177 					List<TimeBlock> jblist = jobNumberToTimeBlocks.get(jobNumber);
178 					if (jblist == null) {
179 						jblist = new ArrayList<TimeBlock>();
180 						jobNumberToTimeBlocks.put(jobNumber, jblist);
181 					}
182 					jblist.add(block);
183 				}
184 			}
185 
186 
187             
188             
189             
190 			
191             
192             
193             
194 			for (Map.Entry<Long, List<ShiftDifferentialRule>> entry : jobNumberToShifts.entrySet()) {
195 				List<ShiftDifferentialRule> shiftDifferentialRules = entry.getValue();
196 				
197 				List<TimeBlock> ruleTimeBlocksPrev = null;
198 				List<TimeBlock> ruleTimeBlocksCurr = jobNumberToTimeBlocks.get(entry.getKey());
199 				if (ruleTimeBlocksCurr != null && ruleTimeBlocksCurr.size() > 0) {
200 					if (jobNumberToTimeBlocksPreviousDay != null)
201 						ruleTimeBlocksPrev = jobNumberToTimeBlocksPreviousDay.get(entry.getKey());
202 					if (ruleTimeBlocksPrev != null && ruleTimeBlocksPrev.size() > 0)
203 						this.sortTimeBlocksInverse(ruleTimeBlocksPrev);
204 					this.sortTimeBlocksNatural(ruleTimeBlocksCurr);
205 				} else {
206 					
207 					
208 					
209 					continue;
210 				}
211 
212 				for (ShiftDifferentialRule rule : shiftDifferentialRules) {
213 					Set<String> fromEarnGroup = TkServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(rule.getFromEarnGroup(), TKUtils.getTimelessDate(timesheetDocument.getCalendarEntry().getBeginPeriodDateTime()));
214 
215                     LocalTime ruleStart = new LocalTime(rule.getBeginTime(), zone);
216                     LocalTime ruleEnd = new LocalTime(rule.getEndTime(), zone);
217 
218 
219 					DateTime shiftEnd = ruleEnd.toDateTime(currentDay);
220 					DateTime shiftStart = ruleStart.toDateTime(currentDay);
221 
222 					if (shiftEnd.isBefore(shiftStart) || shiftEnd.isEqual(shiftStart)) {
223 						shiftEnd = shiftEnd.plusDays(1);
224                     }
225 					Interval shiftInterval = new Interval(shiftStart, shiftEnd);
226 
227 					
228 					BigDecimal hoursBeforeVirtualDay = BigDecimal.ZERO;
229 
230 					
231 					
232 					TimeBlock firstBlockOfCurrentDay = null;
233 					for (TimeBlock b : ruleTimeBlocksCurr) {
234 						if (timeBlockHasEarnCode(fromEarnGroup, b)) {
235 							firstBlockOfCurrentDay = b;
236 							break;
237 						}
238 					}
239 
240 					
241 					Interval previousDayShiftInterval = new Interval(shiftStart.minusDays(1), shiftEnd.minusDays(1));
242 
243                     
244                     Interval evalInterval = null;
245 					if (ruleTimeBlocksPrev != null && ruleTimeBlocksPrev.size() > 0 && dayIsRuleActive(currentDay.minusDays(1), rule)) {
246 						
247 						
248 						if (shiftEnd.isAfter(virtualDay.getEnd())) {
249 							
250 							TimeBlock firstBlockOfPreviousDay = null;
251 							for (TimeBlock b : ruleTimeBlocksPrev) {
252 								if (timeBlockHasEarnCode(fromEarnGroup, b)) {
253 									firstBlockOfPreviousDay = b;
254 									break;
255 								}
256 							}
257 							
258                             
259                             
260                             
261 							if ( (firstBlockOfPreviousDay != null) && (firstBlockOfCurrentDay != null)) {
262 								Interval previousBlockInterval = new Interval(new DateTime(firstBlockOfPreviousDay.getEndTimestamp(), zone), new DateTime(firstBlockOfCurrentDay.getBeginTimestamp(), zone));
263 								Duration blockGapDuration = previousBlockInterval.toDuration();
264 								BigDecimal bgdHours = TKUtils.convertMillisToHours(blockGapDuration.getMillis());
265 								
266 								if (rule.getMaxGap().compareTo(BigDecimal.ZERO) == 0 || bgdHours.compareTo(rule.getMaxGap()) <= 0) {
267 									
268 
269 
270 									
271 									for (int i=0; i<ruleTimeBlocksPrev.size(); i++) {
272 										TimeBlock b = ruleTimeBlocksPrev.get(i);
273 										if (timeBlockHasEarnCode(fromEarnGroup, b)) {
274 											Interval blockInterval = new Interval(new DateTime(b.getBeginTimestamp(), zone), new DateTime(b.getEndTimestamp(), zone));
275 
276 											
277 											if (previousBlockInterval != null) {
278 												blockGapDuration = new Duration(new DateTime(b.getEndTimestamp(), zone), previousBlockInterval.getStart());
279 												bgdHours = TKUtils.convertMillisToHours(blockGapDuration.getMillis());
280 											}
281 
282 											
283 											if (rule.getMaxGap().compareTo(BigDecimal.ZERO) == 0 || bgdHours.compareTo(rule.getMaxGap()) <= 0) {
284 												
285 												if (blockInterval.overlaps(previousDayShiftInterval)) {
286 													BigDecimal hrs = TKUtils.convertMillisToHours(blockInterval.overlap(previousDayShiftInterval).toDurationMillis());
287 													hoursBeforeVirtualDay = hoursBeforeVirtualDay.add(hrs);
288 												}
289 
290 											} else {
291 												
292 												break;
293 											}
294 
295 											previousBlockInterval = blockInterval;
296 
297 										}
298 									}
299 								} else {
300 									
301 								}
302 							}
303 						}
304 					}
305 
306 					BigDecimal hoursToApply = BigDecimal.ZERO;
307 					BigDecimal hoursToApplyPrevious = BigDecimal.ZERO;
308                     
309                     
310                     
311 					if (hoursBeforeVirtualDay.compareTo(rule.getMinHours()) <= 0) {
312 						
313 						hoursToApplyPrevious = hoursBeforeVirtualDay;
314 					}
315 
316 
317 					
318 
319 					TimeBlock previous = null; 
320 					List<TimeBlock> accumulatedBlocks = new ArrayList<TimeBlock>(); 
321                     List<Interval> accumulatedBlockIntervals = new ArrayList<Interval>(); 
322 					
323 					long accumulatedMillis = TKUtils.convertHoursToMillis(hoursBeforeVirtualDay);
324 
325                     boolean previousDayOnly = false; 
326                     if (!dayIsRuleActive(currentDay, rule)) {
327                         if (dayIsRuleActive(currentDay.minusDays(1), rule)) {
328                             previousDayOnly = true;
329                         } else {
330                             
331                             continue;
332                         }
333 
334                     }
335 
336 					
337 
338 
339 
340 
341                     
342                     
343                     
344                     
345                     
346                     
347                     List<TimeBlock> previousBlocksFiltered = (previousPayPeriodPrevDay) ? null : filterBlocksByApplicableEarnGroup(fromEarnGroup, ruleTimeBlocksPrev);
348 
349 					for (TimeBlock current : ruleTimeBlocksCurr) {
350 						if (!timeBlockHasEarnCode(fromEarnGroup, current)) {
351                             
352                             continue;
353                         }
354 
355 						Interval blockInterval = new Interval(new DateTime(current.getBeginTimestamp(), zone), new DateTime(current.getEndTimestamp(), zone));
356 
357 						
358 						
359 						
360 						if (previousDayShiftInterval.overlaps(shiftInterval))
361 							throw new RuntimeException("Interval of greater than 24 hours created in the rules processing.");
362 
363                         
364                         
365                         
366                         
367 						Interval overlap = previousDayShiftInterval.overlap(blockInterval);
368                         evalInterval = previousDayShiftInterval;
369 						if (overlap == null) {
370                             if (hoursToApplyPrevious.compareTo(BigDecimal.ZERO) > 0) {
371                                 
372                                 
373                                 
374                                 
375                                 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
376                                 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
377                                 accumulatedMillis = 0L; 
378                                 hoursToApply = BigDecimal.ZERO;
379                                 hoursToApplyPrevious = BigDecimal.ZERO;
380                             }
381 
382                             
383                             
384                             
385                             
386                             if (previousDayOnly) {
387                                 continue;
388                             }
389 
390 							overlap = shiftInterval.overlap(blockInterval);
391                             evalInterval = shiftInterval;
392                         }
393 
394                         
395                         
396 						if (overlap != null) {
397 							
398 							if (previous != null) {
399 								
400 								if (rule.getMaxGap().compareTo(BigDecimal.ZERO) != 0 && exceedsMaxGap(previous, current, rule.getMaxGap())) {
401 									BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
402                                     this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
403                                     accumulatedMillis = 0L; 
404 									hoursToApply = BigDecimal.ZERO;
405 									hoursToApplyPrevious = BigDecimal.ZERO;
406 								} else {
407 									long millis = overlap.toDurationMillis();
408 									accumulatedMillis  += millis;
409 									hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
410 								}
411 							} else {
412 								
413 								long millis = overlap.toDurationMillis();
414 								accumulatedMillis  += millis;
415 								hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
416 							}
417 							accumulatedBlocks.add(current);
418                             accumulatedBlockIntervals.add(blockInterval);
419 							previous = current; 
420 						} else {
421 							
422 							if (previous != null) {
423 								BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
424                                 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
425 								accumulatedMillis = 0L; 
426 								hoursToApply = BigDecimal.ZERO;
427 								hoursToApplyPrevious = BigDecimal.ZERO;
428 							}
429 						}
430 
431 					}
432 
433 					
434 					
435 					BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
436                     this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
437                 }
438 			}
439 			
440 			jobNumberToTimeBlocksPreviousDay = jobNumberToTimeBlocks;
441             previousPayPeriodPrevDay = false;
442 		}
443 
444 	}
445 
446     @Override
447     public List<ShiftDifferentialRule> getShiftDifferentialRules(String location, String hrSalGroup, String payGrade, Date fromEffdt, Date toEffdt, String active, String showHist) {
448         return shiftDifferentialRuleDao.getShiftDifferentialRules(location, hrSalGroup, payGrade, fromEffdt, toEffdt, active, showHist);
449     }
450 
451     private List<TimeBlock> filterBlocksByApplicableEarnGroup(Set<String> fromEarnGroup, List<TimeBlock> blocks) {
452         List<TimeBlock> filtered;
453 
454         if (blocks == null || blocks.size() == 0)
455             filtered = null;
456         else {
457             filtered = new ArrayList<TimeBlock>();
458             for (TimeBlock b : blocks) {
459                 if (timeBlockHasEarnCode(fromEarnGroup, b))
460                     filtered.add(b);
461             }
462         }
463 
464         return filtered;
465     }
466 
467     private void applyAccumulatedWrapper(BigDecimal accumHours,
468                                          Interval evalInterval,
469                                          List<Interval>accumulatedBlockIntervals,
470                                          List<TimeBlock>accumulatedBlocks,
471                                          List<TimeBlock> previousBlocks,
472                                          BigDecimal hoursToApplyPrevious,
473                                          BigDecimal hoursToApply,
474                                          ShiftDifferentialRule rule) {
475         if (accumHours.compareTo(rule.getMinHours()) >= 0) {
476             this.applyPremium(evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocks, hoursToApplyPrevious, hoursToApply, rule.getEarnCode());
477         }
478         accumulatedBlocks.clear();
479         accumulatedBlockIntervals.clear();
480     }
481 
482 	private void sortTimeBlocksInverse(List<TimeBlock> blocks) {
483 		Collections.sort(blocks, new Comparator<TimeBlock>() { 
484 			public int compare(TimeBlock tb1, TimeBlock tb2) {
485 				if (tb1 != null && tb2 != null)
486 					return -1 * tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
487 				return 0;
488 			}
489 		});
490 	}
491 
492 	private void sortTimeBlocksNatural(List<TimeBlock> blocks) {
493 		Collections.sort(blocks, new Comparator<TimeBlock>() { 
494 			public int compare(TimeBlock tb1, TimeBlock tb2) {
495 				if (tb1 != null && tb2 != null)
496 					return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
497 				return 0;
498 			}
499 		});
500 	}
501 
502     
503 
504 
505 
506 
507 
508 
509 
510 
511 
512 	void applyPremium(Interval shift, List<Interval> blockIntervals, List<TimeBlock> blocks, List<TimeBlock> previousBlocks, BigDecimal initialHours, BigDecimal hours, String earnCode) {
513         for (int i=0; i<blocks.size(); i++) {
514 			TimeBlock b = blocks.get(i);
515 
516             
517 			if (i == 0 && (initialHours.compareTo(BigDecimal.ZERO) > 0)) {
518                 
519                 
520                 if (previousBlocks != null && previousBlocks.size() > 0 && previousBlocks.get(0).getDocumentId().equals(b.getDocumentId())) {
521                     for (TimeBlock pb : previousBlocks) {
522                         BigDecimal lunchSub = this.negativeTimeHourDetailSum(pb); 
523                         initialHours = BigDecimal.ZERO.max(initialHours.add(lunchSub)); 
524                         if (initialHours.compareTo(BigDecimal.ZERO) <= 0) 
525                             break;
526 
527                         
528                         BigDecimal hoursToApply = initialHours.min(pb.getHours().add(lunchSub));
529                         addPremiumTimeHourDetail(pb, hoursToApply, earnCode);
530                         initialHours = initialHours.subtract(hoursToApply, TkConstants.MATH_CONTEXT);
531                         if (initialHours.compareTo(BigDecimal.ZERO) <= 0)
532                             break;
533                     }
534                 } else {
535 				    addPremiumTimeHourDetail(b, initialHours, earnCode);
536                 }
537             }
538 
539             BigDecimal lunchSub = this.negativeTimeHourDetailSum(b); 
540             hours = BigDecimal.ZERO.max(hours.add(lunchSub)); 
541 
542 			if (hours.compareTo(BigDecimal.ZERO) > 0) {
543                 Interval blockInterval = blockIntervals.get(i);
544                 Interval overlapInterval = shift.overlap(blockInterval);
545                 if (overlapInterval == null)
546                     continue;
547 
548                 long overlap = overlapInterval.toDurationMillis();
549                 BigDecimal hoursMax = TKUtils.convertMillisToHours(overlap); 
550                 
551                 
552                 BigDecimal hoursToApply = hours.min(hoursMax.add(lunchSub));
553 
554                 addPremiumTimeHourDetail(b, hoursToApply, earnCode);
555 				hours = hours.subtract(hoursToApply, TkConstants.MATH_CONTEXT);
556 			}
557 		}
558 	}
559 
560 	void addPremiumTimeHourDetail(TimeBlock block, BigDecimal hours, String earnCode) {
561 		List<TimeHourDetail> details = block.getTimeHourDetails();
562 		TimeHourDetail premium = new TimeHourDetail();
563 		premium.setHours(hours);
564 		premium.setEarnCode(earnCode);
565 		premium.setTkTimeBlockId(block.getTkTimeBlockId());
566 		details.add(premium);
567 	}
568 
569 	
570 
571 
572 
573 
574 
575 
576 
577 
578 	boolean exceedsMaxGap(TimeBlock previous, TimeBlock current, BigDecimal maxGap) {
579 		long difference = current.getBeginTimestamp().getTime() - previous.getEndTimestamp().getTime();
580 		BigDecimal gapMinutes = TKUtils.convertMillisToMinutes(difference);
581 
582 		return (gapMinutes.compareTo(maxGap) > 0);
583 	}
584 
585 	public void setShiftDifferentialRuleDao(ShiftDifferentialRuleDao shiftDifferentialRuleDao) {
586 		this.shiftDifferentialRuleDao = shiftDifferentialRuleDao;
587 	}
588 
589 	@Override
590 	public ShiftDifferentialRule getShiftDifferentialRule(String tkShiftDifferentialRuleId) {
591 		return this.shiftDifferentialRuleDao.findShiftDifferentialRule(tkShiftDifferentialRuleId);
592 	}
593 
594 	@Override
595 	public List<ShiftDifferentialRule> getShiftDifferentalRules(String location, String hrSalGroup, String payGrade, String pyCalendarGroup, Date asOfDate) {
596 		List<ShiftDifferentialRule> sdrs = new ArrayList<ShiftDifferentialRule>();
597 		
598 		
599 	    sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, payGrade, pyCalendarGroup, asOfDate));
600 
601 		
602 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, "%", pyCalendarGroup, asOfDate));
603 
604 		
605 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", payGrade, pyCalendarGroup, asOfDate));
606 
607 		
608 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", "%", pyCalendarGroup, asOfDate));
609 
610 		
611 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, payGrade, pyCalendarGroup, asOfDate));
612 
613 		
614 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, "%", pyCalendarGroup, asOfDate));
615 
616 		
617 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", payGrade, pyCalendarGroup, asOfDate));
618 
619 		
620 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", "%", pyCalendarGroup, asOfDate));
621 
622 		
623 	    sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, payGrade, "%", asOfDate));
624 
625 		
626 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, "%", "%", asOfDate));
627 
628 		
629 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", payGrade, "%", asOfDate));
630 
631 		
632 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", "%", "%", asOfDate));
633 
634 		
635 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, payGrade, "%", asOfDate));
636 
637 		
638 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, "%", "%", asOfDate));
639 
640 		
641 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", payGrade, "%", asOfDate));
642 
643 		
644 		sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", "%", "%", asOfDate));
645 
646 		return sdrs;
647 	}
648 
649 	private boolean dayIsRuleActive(DateTime currentDate, ShiftDifferentialRule sdr) {
650 		boolean active = false;
651 
652 		switch (currentDate.getDayOfWeek()) {
653 		case DateTimeConstants.MONDAY:
654 			active = sdr.isMonday();
655 			break;
656 		case DateTimeConstants.TUESDAY:
657 			active = sdr.isTuesday();
658 			break;
659 		case DateTimeConstants.WEDNESDAY:
660 			active = sdr.isWednesday();
661 			break;
662 		case DateTimeConstants.THURSDAY:
663 			active = sdr.isThursday();
664 			break;
665 		case DateTimeConstants.FRIDAY:
666 			active = sdr.isFriday();
667 			break;
668 		case DateTimeConstants.SATURDAY:
669 			active = sdr.isSaturday();
670 			break;
671 		case DateTimeConstants.SUNDAY:
672 			active = sdr.isSunday();
673 			break;
674 		}
675 
676 		return active;
677 	}
678 
679 	@Override
680 	public void saveOrUpdate(List<ShiftDifferentialRule> shiftDifferentialRules) {
681 		shiftDifferentialRuleDao.saveOrUpdate(shiftDifferentialRules);
682 	}
683 
684 	@Override
685 	public void saveOrUpdate(ShiftDifferentialRule shiftDifferentialRule) {
686 		shiftDifferentialRuleDao.saveOrUpdate(shiftDifferentialRule);
687 	}
688 
689 }