1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.kpme.tklm.time.rules.shiftdifferential.service;
17
18 import java.math.BigDecimal;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.HashMap;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27
28 import org.apache.commons.collections.CollectionUtils;
29 import org.apache.log4j.Logger;
30 import org.joda.time.DateTime;
31 import org.joda.time.DateTimeConstants;
32 import org.joda.time.DateTimeZone;
33 import org.joda.time.Duration;
34 import org.joda.time.Interval;
35 import org.joda.time.LocalDate;
36 import org.joda.time.LocalTime;
37 import org.kuali.kpme.core.KPMENamespace;
38 import org.kuali.kpme.core.calendar.entry.CalendarEntry;
39 import org.kuali.kpme.core.job.Job;
40 import org.kuali.kpme.core.permission.KPMEPermissionTemplate;
41 import org.kuali.kpme.core.principal.PrincipalHRAttributes;
42 import org.kuali.kpme.core.role.KPMERoleMemberAttribute;
43 import org.kuali.kpme.core.service.HrServiceLocator;
44 import org.kuali.kpme.core.util.HrConstants;
45 import org.kuali.kpme.core.util.TKUtils;
46 import org.kuali.kpme.tklm.time.rules.shiftdifferential.ShiftDifferentialRule;
47 import org.kuali.kpme.tklm.time.rules.shiftdifferential.dao.ShiftDifferentialRuleDao;
48 import org.kuali.kpme.tklm.time.service.TkServiceLocator;
49 import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
50 import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetail;
51 import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
52 import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
53 import org.kuali.kpme.tklm.time.workflow.TimesheetDocumentHeader;
54 import org.kuali.rice.kim.api.KimConstants;
55 import org.kuali.rice.kim.api.services.KimApiServiceLocator;
56 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
57
58
59 public class ShiftDifferentialRuleServiceImpl implements ShiftDifferentialRuleService {
60
61 @SuppressWarnings("unused")
62 private static final Logger LOG = Logger.getLogger(ShiftDifferentialRuleServiceImpl.class);
63
64
65
66
67 private ShiftDifferentialRuleDao shiftDifferentialRuleDao = null;
68
69 private Map<Long,Set<ShiftDifferentialRule>> getJobNumberToShiftRuleMap(TimesheetDocument timesheetDocument) {
70 Map<Long,Set<ShiftDifferentialRule>> jobNumberToShifts = new HashMap<Long,Set<ShiftDifferentialRule>>();
71 PrincipalHRAttributes principalCal = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(timesheetDocument.getPrincipalId(),timesheetDocument.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate());
72
73 for (Job job : timesheetDocument.getJobs()) {
74 List<ShiftDifferentialRule> shiftDifferentialRules = getShiftDifferentalRules(job.getLocation(),job.getHrSalGroup(),job.getPayGrade(),principalCal.getPayCalendar(),
75 timesheetDocument.getCalendarEntry().getEndPeriodFullDateTime().toLocalDate());
76 if (shiftDifferentialRules.size() > 0)
77 jobNumberToShifts.put(job.getJobNumber(), new HashSet<ShiftDifferentialRule>(shiftDifferentialRules));
78 }
79
80 return jobNumberToShifts;
81 }
82
83 private Map<Long,List<TimeBlock>> getPreviousPayPeriodLastDayJobToTimeBlockMap(TimesheetDocument timesheetDocument, Map<Long,Set<ShiftDifferentialRule>> jobNumberToShifts) {
84 Map<Long, List<TimeBlock>> jobNumberToTimeBlocksPreviousDay = null;
85
86
87
88 List<TimeBlock> prevBlocks = TkServiceLocator.getTimesheetService().getPrevDocumentTimeBlocks(timesheetDocument.getPrincipalId(), timesheetDocument.getDocumentHeader().getBeginDateTime());
89 if (prevBlocks.size() > 0) {
90 TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(timesheetDocument.getPrincipalId(), timesheetDocument.getDocumentHeader().getBeginDateTime().toDateTime());
91 if (prevTdh != null) {
92 CalendarEntry prevPayCalendarEntry = HrServiceLocator.getCalendarEntryService().getCalendarDatesByPayEndDate(timesheetDocument.getPrincipalId(), prevTdh.getEndDateTime(), HrConstants.PAY_CALENDAR_TYPE);
93 TkTimeBlockAggregate prevTimeAggregate = new TkTimeBlockAggregate(prevBlocks, prevPayCalendarEntry, prevPayCalendarEntry.getCalendarObj(), true);
94 List<List<TimeBlock>> dayBlocks = prevTimeAggregate.getDayTimeBlockList();
95 List<TimeBlock> previousPeriodLastDayBlocks = dayBlocks.get(dayBlocks.size() - 1);
96
97 if (previousPeriodLastDayBlocks.size() > 0) {
98 jobNumberToTimeBlocksPreviousDay = new HashMap<Long, List<TimeBlock>>();
99
100 for (TimeBlock block : previousPeriodLastDayBlocks) {
101
102
103 Long jobNumber = block.getJobNumber();
104 if (jobNumberToShifts.containsKey(jobNumber)) {
105
106 List<TimeBlock> jblist = jobNumberToTimeBlocksPreviousDay.get(jobNumber);
107 if (jblist == null) {
108 jblist = new ArrayList<TimeBlock>();
109 jobNumberToTimeBlocksPreviousDay.put(jobNumber, jblist);
110 }
111 jblist.add(block);
112 }
113 }
114 }
115 }
116 }
117
118 return jobNumberToTimeBlocksPreviousDay;
119 }
120
121 private boolean timeBlockHasEarnCode(Set<String> earnCodes, TimeBlock block) {
122 boolean present = false;
123
124 if (block != null && earnCodes != null)
125 present = earnCodes.contains(block.getEarnCode());
126
127 return present;
128 }
129
130
131
132
133
134
135
136
137
138
139 private BigDecimal negativeTimeHourDetailSum(TimeBlock block) {
140 BigDecimal sum = BigDecimal.ZERO;
141
142 if (block != null) {
143 List<TimeHourDetail> details = block.getTimeHourDetails();
144 for (TimeHourDetail detail : details) {
145 if (detail.getEarnCode().equals(HrConstants.LUNCH_EARN_CODE)) {
146 sum = sum.add(detail.getHours());
147 }
148 }
149 }
150
151 return sum;
152 }
153
154 @Override
155 public void processShiftDifferentialRules(TimesheetDocument timesheetDocument, TkTimeBlockAggregate aggregate) {
156 DateTimeZone zone = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
157 List<List<TimeBlock>> blockDays = aggregate.getDayTimeBlockList();
158 DateTime periodStartDateTime = timesheetDocument.getCalendarEntry().getBeginPeriodLocalDateTime().toDateTime(zone);
159 Map<Long,Set<ShiftDifferentialRule>> jobNumberToShifts = getJobNumberToShiftRuleMap(timesheetDocument);
160
161
162
163 if (jobNumberToShifts.isEmpty()) {
164 return;
165 }
166
167
168
169
170
171
172 boolean previousPayPeriodPrevDay = true;
173 Map<Long, List<TimeBlock>> jobNumberToTimeBlocksPreviousDay =
174 getPreviousPayPeriodLastDayJobToTimeBlockMap(timesheetDocument, jobNumberToShifts);
175
176
177
178
179 for (int pos = 0; pos < blockDays.size(); pos++) {
180 List<TimeBlock> blocks = blockDays.get(pos);
181 if (blocks.isEmpty()) {
182 continue;
183 }
184 DateTime currentDay = periodStartDateTime.plusDays(pos);
185 Interval virtualDay = new Interval(currentDay, currentDay.plusDays(1));
186
187
188
189
190
191
192
193 Map<Long, List<TimeBlock>> jobNumberToTimeBlocks = new HashMap<Long,List<TimeBlock>>();
194 for (TimeBlock block : blocks) {
195 Long jobNumber = block.getJobNumber();
196 if (jobNumberToShifts.containsKey(jobNumber)) {
197 List<TimeBlock> jblist = jobNumberToTimeBlocks.get(jobNumber);
198 if (jblist == null) {
199 jblist = new ArrayList<TimeBlock>();
200 jobNumberToTimeBlocks.put(jobNumber, jblist);
201 }
202 jblist.add(block);
203 }
204 }
205
206
207
208
209
210
211
212
213
214 for (Map.Entry<Long, Set<ShiftDifferentialRule>> entry : jobNumberToShifts.entrySet()) {
215 Set<ShiftDifferentialRule> shiftDifferentialRules = entry.getValue();
216
217 List<TimeBlock> ruleTimeBlocksPrev = null;
218 List<TimeBlock> ruleTimeBlocksCurr = jobNumberToTimeBlocks.get(entry.getKey());
219 if (ruleTimeBlocksCurr != null && ruleTimeBlocksCurr.size() > 0) {
220 if (jobNumberToTimeBlocksPreviousDay != null)
221 ruleTimeBlocksPrev = jobNumberToTimeBlocksPreviousDay.get(entry.getKey());
222 if (ruleTimeBlocksPrev != null && ruleTimeBlocksPrev.size() > 0)
223 this.sortTimeBlocksInverse(ruleTimeBlocksPrev);
224 this.sortTimeBlocksNatural(ruleTimeBlocksCurr);
225 } else {
226
227
228
229 continue;
230 }
231
232 for (ShiftDifferentialRule rule : shiftDifferentialRules) {
233 Set<String> fromEarnGroup = HrServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(rule.getFromEarnGroup(), timesheetDocument.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate());
234
235
236
237 LocalTime ruleStart = new LocalTime(rule.getBeginTime());
238 LocalTime ruleEnd = new LocalTime(rule.getEndTime());
239
240
241 DateTime shiftEnd = ruleEnd.toDateTime(currentDay);
242 DateTime shiftStart = ruleStart.toDateTime(currentDay);
243
244 if (shiftEnd.isBefore(shiftStart) || shiftEnd.isEqual(shiftStart)) {
245 shiftEnd = shiftEnd.plusDays(1);
246 }
247 Interval shiftInterval = new Interval(shiftStart, shiftEnd);
248
249
250 BigDecimal hoursBeforeVirtualDay = BigDecimal.ZERO;
251
252
253
254 TimeBlock firstBlockOfCurrentDay = null;
255 for (TimeBlock b : ruleTimeBlocksCurr) {
256 if (timeBlockHasEarnCode(fromEarnGroup, b)) {
257 firstBlockOfCurrentDay = b;
258 break;
259 }
260 }
261
262
263 Interval previousDayShiftInterval = new Interval(shiftStart.minusDays(1), shiftEnd.minusDays(1));
264
265
266 Interval evalInterval = null;
267 if (ruleTimeBlocksPrev != null && ruleTimeBlocksPrev.size() > 0 && dayIsRuleActive(currentDay.minusDays(1), rule)) {
268
269
270 if (shiftEnd.isAfter(virtualDay.getEnd())) {
271
272 TimeBlock firstBlockOfPreviousDay = null;
273 for (TimeBlock b : ruleTimeBlocksPrev) {
274 if (timeBlockHasEarnCode(fromEarnGroup, b)) {
275 firstBlockOfPreviousDay = b;
276 break;
277 }
278 }
279
280
281
282
283 if ( (firstBlockOfPreviousDay != null) && (firstBlockOfCurrentDay != null)) {
284 Interval previousBlockInterval = new Interval(firstBlockOfPreviousDay.getEndDateTime().withZone(zone), firstBlockOfCurrentDay.getBeginDateTime().withZone(zone));
285 Duration blockGapDuration = previousBlockInterval.toDuration();
286 BigDecimal bgdMinutes = TKUtils.convertMillisToMinutes(blockGapDuration.getMillis());
287
288 if (rule.getMaxGap().compareTo(BigDecimal.ZERO) == 0 || bgdMinutes.compareTo(rule.getMaxGap()) <= 0) {
289
290
291
292
293 for (int i=0; i<ruleTimeBlocksPrev.size(); i++) {
294 TimeBlock b = ruleTimeBlocksPrev.get(i);
295 if (timeBlockHasEarnCode(fromEarnGroup, b)) {
296 Interval blockInterval = new Interval(b.getBeginDateTime().withZone(zone), b.getEndDateTime().withZone(zone));
297
298
299 if (previousBlockInterval != null) {
300 blockGapDuration = new Duration(b.getEndDateTime().withZone(zone), previousBlockInterval.getStart());
301 bgdMinutes = TKUtils.convertMillisToMinutes(blockGapDuration.getMillis());
302 }
303
304
305 if (rule.getMaxGap().compareTo(BigDecimal.ZERO) == 0 || bgdMinutes.compareTo(rule.getMaxGap()) <= 0) {
306
307 if (blockInterval.overlaps(previousDayShiftInterval)) {
308 BigDecimal hrs = TKUtils.convertMillisToHours(blockInterval.overlap(previousDayShiftInterval).toDurationMillis());
309 hoursBeforeVirtualDay = hoursBeforeVirtualDay.add(hrs);
310 }
311
312 } else {
313
314 break;
315 }
316
317 previousBlockInterval = blockInterval;
318
319 }
320 }
321 } else {
322
323 }
324 }
325 }
326 }
327
328 BigDecimal hoursToApply = BigDecimal.ZERO;
329 BigDecimal hoursToApplyPrevious = BigDecimal.ZERO;
330
331
332
333 if (hoursBeforeVirtualDay.compareTo(rule.getMinHours()) <= 0) {
334
335 hoursToApplyPrevious = hoursBeforeVirtualDay;
336 }
337
338
339
340
341 TimeBlock previous = null;
342 List<TimeBlock> accumulatedBlocks = new ArrayList<TimeBlock>();
343 List<Interval> accumulatedBlockIntervals = new ArrayList<Interval>();
344
345 long accumulatedMillis = TKUtils.convertHoursToMillis(hoursBeforeVirtualDay);
346
347 boolean previousDayOnly = false;
348 if (!dayIsRuleActive(currentDay, rule)) {
349 if (dayIsRuleActive(currentDay.minusDays(1), rule)) {
350 previousDayOnly = true;
351 } else {
352
353 continue;
354 }
355
356 }
357
358
359
360
361
362
363
364
365
366
367
368
369 List<TimeBlock> previousBlocksFiltered = (previousPayPeriodPrevDay) ? null : filterBlocksByApplicableEarnGroup(fromEarnGroup, ruleTimeBlocksPrev);
370
371 for (TimeBlock current : ruleTimeBlocksCurr) {
372 if (!timeBlockHasEarnCode(fromEarnGroup, current)) {
373
374 continue;
375 }
376
377 Interval blockInterval = new Interval(current.getBeginDateTime().withZone(zone), current.getEndDateTime().withZone(zone));
378
379
380
381
382 if (previousDayShiftInterval.overlaps(shiftInterval)) {
383 LOG.error("Interval of greater than 24 hours created in the rules processing.");
384 return;
385
386 }
387
388
389
390
391
392 Interval overlap = previousDayShiftInterval.overlap(blockInterval);
393 Interval overlapCurrentDay = shiftInterval.overlap(blockInterval);
394 evalInterval = previousDayShiftInterval;
395 boolean overlapFromPreviousDay = true;
396 if (overlap == null) {
397 if (hoursToApplyPrevious.compareTo(BigDecimal.ZERO) > 0) {
398
399
400
401
402 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
403 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
404 accumulatedMillis = 0L;
405 hoursToApply = BigDecimal.ZERO;
406 hoursToApplyPrevious = BigDecimal.ZERO;
407 }
408
409
410
411
412
413 if (previousDayOnly) {
414 continue;
415 }
416
417 overlap = shiftInterval.overlap(blockInterval);
418 evalInterval = shiftInterval;
419 overlapFromPreviousDay = false;
420 }
421
422
423
424
425 if (overlap != null) {
426
427 if (previous != null) {
428
429
430 Interval previousBlockInterval = new Interval(previous.getBeginDateTime().withZone(zone), previous.getEndDateTime().withZone(zone));
431 if(evalInterval.overlaps(previousBlockInterval)) {
432
433 if (rule.getMaxGap().compareTo(BigDecimal.ZERO) != 0 && exceedsMaxGap(previous, current, rule.getMaxGap())) {
434 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
435 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
436 accumulatedMillis = 0L;
437 hoursToApply = BigDecimal.ZERO;
438 hoursToApplyPrevious = BigDecimal.ZERO;
439
440
441 Interval currentShiftOverlap = shiftInterval.overlap(blockInterval);
442 if (currentShiftOverlap != null) {
443 long millis = currentShiftOverlap.toDurationMillis();
444 accumulatedMillis += millis;
445 hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
446 }
447 } else {
448
449
450 List<Interval> overlapIntervals = getOverlappingIntervals(blockInterval,rule);
451 for(Interval overlapWithShift : overlapIntervals){
452 long millis = overlapWithShift.toDurationMillis();
453 accumulatedMillis += millis;
454 hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
455 }
456 }
457 } else {
458
459
460 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
461 this.applyAccumulatedWrapper(accumHours, previousDayShiftInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
462
463 long millis = overlap.toDurationMillis();
464 accumulatedMillis = millis;
465 hoursToApply = TKUtils.convertMillisToHours(millis);
466 }
467 } else {
468
469 boolean previousDayActive = dayIsRuleActive(previousDayShiftInterval.getStart(), rule);
470 if(!previousDayActive && overlapFromPreviousDay) {
471 continue;
472 } else {
473
474 List<Interval> overlapIntervals = getOverlappingIntervals(blockInterval,rule);
475 for(Interval overlapWithShift : overlapIntervals){
476 long millis = overlapWithShift.toDurationMillis();
477 accumulatedMillis += millis;
478 hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
479 }
480 }
481 }
482 accumulatedBlocks.add(current);
483 accumulatedBlockIntervals.add(blockInterval);
484 previous = current;
485 } else {
486
487 if (previous != null) {
488 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
489 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
490 accumulatedMillis = 0L;
491 hoursToApply = BigDecimal.ZERO;
492 hoursToApplyPrevious = BigDecimal.ZERO;
493 }
494 }
495
496 }
497
498
499
500 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
501 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
502 }
503 }
504
505 jobNumberToTimeBlocksPreviousDay = jobNumberToTimeBlocks;
506 previousPayPeriodPrevDay = false;
507 }
508
509 }
510
511 @Override
512 public List<ShiftDifferentialRule> getShiftDifferentialRules(String userPrincipalId, String location, String hrSalGroup, String payGrade, LocalDate fromEffdt, LocalDate toEffdt, String active, String showHist) {
513 List<ShiftDifferentialRule> results = new ArrayList<ShiftDifferentialRule>();
514
515 List<ShiftDifferentialRule> shiftDifferentialRuleObjs = shiftDifferentialRuleDao.getShiftDifferentialRules(location, hrSalGroup, payGrade, fromEffdt, toEffdt, active, showHist);
516
517 for (ShiftDifferentialRule shiftDifferentialRuleObj : shiftDifferentialRuleObjs) {
518 Map<String, String> roleQualification = new HashMap<String, String>();
519 roleQualification.put(KimConstants.AttributeConstants.PRINCIPAL_ID, userPrincipalId);
520 roleQualification.put(KPMERoleMemberAttribute.LOCATION.getRoleMemberAttributeName(), shiftDifferentialRuleObj.getLocation());
521
522 Map<String, String> permissionDetails = new HashMap<String, String>();
523 permissionDetails.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, KRADServiceLocatorWeb.getDocumentDictionaryService().getMaintenanceDocumentTypeName(ShiftDifferentialRule.class));
524
525 if (!KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KPMENamespace.KPME_WKFLW.getNamespaceCode(),
526 KPMEPermissionTemplate.VIEW_KPME_RECORD.getPermissionTemplateName(), permissionDetails)
527 || KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(userPrincipalId, KPMENamespace.KPME_WKFLW.getNamespaceCode(),
528 KPMEPermissionTemplate.VIEW_KPME_RECORD.getPermissionTemplateName(), permissionDetails, roleQualification)) {
529 results.add(shiftDifferentialRuleObj);
530 }
531 }
532
533 return results;
534 }
535
536 private List<TimeBlock> filterBlocksByApplicableEarnGroup(Set<String> fromEarnGroup, List<TimeBlock> blocks) {
537 List<TimeBlock> filtered;
538
539 if (blocks == null || blocks.size() == 0)
540 filtered = null;
541 else {
542 filtered = new ArrayList<TimeBlock>();
543 for (TimeBlock b : blocks) {
544 if (timeBlockHasEarnCode(fromEarnGroup, b))
545 filtered.add(b);
546 }
547 }
548
549 return filtered;
550 }
551
552 private void applyAccumulatedWrapper(BigDecimal accumHours,
553 Interval evalInterval,
554 List<Interval>accumulatedBlockIntervals,
555 List<TimeBlock>accumulatedBlocks,
556 List<TimeBlock> previousBlocks,
557 BigDecimal hoursToApplyPrevious,
558 BigDecimal hoursToApply,
559 ShiftDifferentialRule rule) {
560 if (accumHours.compareTo(rule.getMinHours()) >= 0) {
561 this.applyPremium(evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocks, hoursToApplyPrevious, hoursToApply, rule.getEarnCode(), rule);
562 }
563 accumulatedBlocks.clear();
564 accumulatedBlockIntervals.clear();
565 }
566
567 private void sortTimeBlocksInverse(List<TimeBlock> blocks) {
568 Collections.sort(blocks, new Comparator<TimeBlock>() {
569 public int compare(TimeBlock tb1, TimeBlock tb2) {
570 if (tb1 != null && tb2 != null)
571 return -1 * tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
572 return 0;
573 }
574 });
575 }
576
577 private void sortTimeBlocksNatural(List<TimeBlock> blocks) {
578 Collections.sort(blocks, new Comparator<TimeBlock>() {
579 public int compare(TimeBlock tb1, TimeBlock tb2) {
580 if (tb1 != null && tb2 != null)
581 return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
582 return 0;
583 }
584 });
585 }
586
587
588
589
590
591
592
593
594
595
596
597 protected void applyPremium(Interval shift, List<Interval> blockIntervals, List<TimeBlock> blocks, List<TimeBlock> previousBlocks, BigDecimal initialHours, BigDecimal hours, String earnCode, ShiftDifferentialRule rule) {
598 Map<Interval, Long> nextGaps = new HashMap<Interval, Long>();
599 List<Interval> possibleShifts = new ArrayList<Interval>(3);
600 possibleShifts.add(new Interval(shift.getStart().minusDays(1), shift.getEnd().minusDays(1)));
601 possibleShifts.add(shift);
602 possibleShifts.add(new Interval(shift.getStart().plusDays(1), shift.getEnd().plusDays(1)));
603 Map<Interval, Long> shiftOverlapMillis = new HashMap<Interval, Long>();
604 Map<Interval, Interval> overlapToShift = new HashMap<Interval, Interval>();
605 List<Interval> allOverlaps = new ArrayList<Interval>();
606 for (Interval interval : blockIntervals) {
607 allOverlaps.addAll(getOverlappingIntervals(interval, rule));
608 }
609 if (rule.getMaxGap().compareTo(BigDecimal.ZERO) != 0) {
610 for (int i = 0; i < allOverlaps.size(); i++) {
611 Long gap = 0L;
612 if (i+1 < allOverlaps.size()) {
613 gap = allOverlaps.get(i+1).getStartMillis() - allOverlaps.get(i).getEndMillis();
614 }
615 nextGaps.put(allOverlaps.get(i), gap);
616 }
617 }
618 for (Interval overlap : allOverlaps) {
619 for (Interval possibleShift : possibleShifts) {
620 if (possibleShift.overlaps(overlap)) {
621 overlapToShift.put(overlap, possibleShift);
622 if (!shiftOverlapMillis.containsKey(possibleShift)) {
623 shiftOverlapMillis.put(possibleShift, possibleShift.overlap(overlap).toDurationMillis());
624 } else {
625 shiftOverlapMillis.put(possibleShift, (shiftOverlapMillis.get(possibleShift) + possibleShift.overlap(overlap).toDurationMillis()));
626 }
627
628
629 }
630 }
631 }
632
633 for (int i=0; i<blocks.size(); i++) {
634 TimeBlock b = blocks.get(i);
635
636
637 if (i == 0 && (initialHours.compareTo(BigDecimal.ZERO) > 0)) {
638
639
640 if (previousBlocks != null && previousBlocks.size() > 0 && previousBlocks.get(0).getDocumentId().equals(b.getDocumentId())) {
641 for (TimeBlock pb : previousBlocks) {
642 BigDecimal lunchSub = this.negativeTimeHourDetailSum(pb);
643 initialHours = BigDecimal.ZERO.max(initialHours.add(lunchSub));
644 if (initialHours.compareTo(BigDecimal.ZERO) <= 0)
645 break;
646
647
648 BigDecimal hoursToApply = initialHours.min(pb.getHours().add(lunchSub));
649 addPremiumTimeHourDetail(pb, hoursToApply, earnCode);
650 initialHours = initialHours.subtract(hoursToApply, HrConstants.MATH_CONTEXT);
651 if (initialHours.compareTo(BigDecimal.ZERO) <= 0)
652 break;
653 }
654 } else {
655 addPremiumTimeHourDetail(b, initialHours, earnCode);
656 }
657 }
658
659 BigDecimal lunchSub = this.negativeTimeHourDetailSum(b);
660 hours = BigDecimal.ZERO.max(hours.add(lunchSub));
661
662 if (hours.compareTo(BigDecimal.ZERO) > 0) {
663 Interval blockInterval = blockIntervals.get(i);
664 List<Interval> overlapIntervals = getOverlappingIntervals(blockInterval, rule);
665 BigDecimal allHoursToApply = BigDecimal.ZERO;
666 for (Interval overlapInterval : overlapIntervals) {
667 if (overlapInterval == null) {
668 continue;
669 }
670
671 long minMillis = TKUtils.convertHoursToMillis(rule.getMinHours());
672 long overlap = overlapInterval.toDurationMillis();
673 BigDecimal hoursMax = TKUtils.convertMillisToHours(overlap);
674
675
676
677 Interval overlapShift = overlapToShift.get(overlapInterval);
678 Long totalOverlapMillisInShift = shiftOverlapMillis.get(overlapShift);
679 if (totalOverlapMillisInShift >= minMillis
680 || overlapIntervals.size() == 1) {
681 BigDecimal hoursToApply = hours.min(hoursMax.add(lunchSub));
682 allHoursToApply = allHoursToApply.add(hoursToApply);
683 }
684 }
685 if (allHoursToApply.compareTo(BigDecimal.ZERO) > 0) {
686 addPremiumTimeHourDetail(b, allHoursToApply, earnCode);
687 hours = hours.subtract(allHoursToApply, HrConstants.MATH_CONTEXT);
688 }
689 }
690 }
691 }
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752 protected List<Interval> getOverlappingIntervals(Interval timeBlockOverlap, ShiftDifferentialRule rule){
753 List<Interval> overlappingIntervals = new ArrayList<Interval>();
754
755 DateTimeZone zone = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
756
757
758 DateTime previousDay = timeBlockOverlap.getStart().minusDays(1);
759 if(dayIsRuleActive(previousDay,rule)){
760 LocalTime ruleStart = new LocalTime(rule.getBeginTime(), zone);
761 LocalTime ruleEnd = new LocalTime(rule.getEndTime(), zone);
762
763 DateTime shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getStart());
764 if (ruleEnd.isAfter(ruleStart)) {
765 shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getEnd().minusDays(1));
766 }
767 DateTime shiftStart = ruleStart.toDateTime(previousDay);
768 Interval shiftInterval = new Interval(shiftStart,shiftEnd);
769 Interval overlapInterval = shiftInterval.overlap(timeBlockOverlap);
770 if(overlapInterval != null){
771 overlappingIntervals.add(overlapInterval);
772 }
773 }
774
775 DateTime currentDay = timeBlockOverlap.getStart();
776 if(dayIsRuleActive(currentDay,rule)){
777 LocalTime ruleStart = new LocalTime(rule.getBeginTime(), zone);
778 LocalTime ruleEnd = new LocalTime(rule.getEndTime(), zone);
779
780 DateTime shiftEnd = null;
781 if(ruleEnd.isBefore(ruleStart) || ruleEnd.isEqual(ruleStart)){
782 shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getEnd().plusDays(1));
783 } else {
784 shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getEnd());
785 }
786 DateTime shiftStart = ruleStart.toDateTime(currentDay);
787 Interval shiftInterval = new Interval(shiftStart,shiftEnd);
788 Interval overlapInterval = shiftInterval.overlap(timeBlockOverlap);
789 if(overlapInterval != null){
790 overlappingIntervals.add(overlapInterval);
791 }
792 }
793
794 return overlappingIntervals;
795
796 }
797
798 void addPremiumTimeHourDetail(TimeBlock block, BigDecimal hours, String earnCode) {
799 List<TimeHourDetail> details = block.getTimeHourDetails();
800 TimeHourDetail premium = new TimeHourDetail();
801 premium.setHours(hours);
802 premium.setEarnCode(earnCode);
803 premium.setTkTimeBlockId(block.getTkTimeBlockId());
804 details.add(premium);
805 }
806
807
808
809
810
811
812
813
814
815
816 boolean exceedsMaxGap(TimeBlock previous, TimeBlock current, BigDecimal maxGap) {
817 long difference = current.getBeginTimestamp().getTime() - previous.getEndTimestamp().getTime();
818 BigDecimal gapMinutes = TKUtils.convertMillisToMinutes(difference);
819
820 return (gapMinutes.compareTo(maxGap) > 0);
821 }
822
823 public void setShiftDifferentialRuleDao(ShiftDifferentialRuleDao shiftDifferentialRuleDao) {
824 this.shiftDifferentialRuleDao = shiftDifferentialRuleDao;
825 }
826
827 @Override
828 public ShiftDifferentialRule getShiftDifferentialRule(String tkShiftDifferentialRuleId) {
829 return this.shiftDifferentialRuleDao.findShiftDifferentialRule(tkShiftDifferentialRuleId);
830 }
831
832 @Override
833 public List<ShiftDifferentialRule> getShiftDifferentalRules(String location, String hrSalGroup, String payGrade, String pyCalendarGroup, LocalDate asOfDate) {
834 List<ShiftDifferentialRule> sdrs = new ArrayList<ShiftDifferentialRule>();
835
836
837 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, payGrade, pyCalendarGroup, asOfDate));
838
839
840 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, "%", pyCalendarGroup, asOfDate));
841
842
843 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", payGrade, pyCalendarGroup, asOfDate));
844
845
846 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", "%", pyCalendarGroup, asOfDate));
847
848
849 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, payGrade, pyCalendarGroup, asOfDate));
850
851
852 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, "%", pyCalendarGroup, asOfDate));
853
854
855 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", payGrade, pyCalendarGroup, asOfDate));
856
857
858 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", "%", pyCalendarGroup, asOfDate));
859
860
861 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, payGrade, "%", asOfDate));
862
863
864 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, "%", "%", asOfDate));
865
866
867 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", payGrade, "%", asOfDate));
868
869
870 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", "%", "%", asOfDate));
871
872
873 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, payGrade, "%", asOfDate));
874
875
876 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, "%", "%", asOfDate));
877
878
879 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", payGrade, "%", asOfDate));
880
881
882 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", "%", "%", asOfDate));
883
884 return sdrs;
885 }
886
887 private boolean dayIsRuleActive(DateTime currentDate, ShiftDifferentialRule sdr) {
888 boolean active = false;
889
890 switch (currentDate.getDayOfWeek()) {
891 case DateTimeConstants.MONDAY:
892 active = sdr.isMonday();
893 break;
894 case DateTimeConstants.TUESDAY:
895 active = sdr.isTuesday();
896 break;
897 case DateTimeConstants.WEDNESDAY:
898 active = sdr.isWednesday();
899 break;
900 case DateTimeConstants.THURSDAY:
901 active = sdr.isThursday();
902 break;
903 case DateTimeConstants.FRIDAY:
904 active = sdr.isFriday();
905 break;
906 case DateTimeConstants.SATURDAY:
907 active = sdr.isSaturday();
908 break;
909 case DateTimeConstants.SUNDAY:
910 active = sdr.isSunday();
911 break;
912 }
913
914 return active;
915 }
916
917 @Override
918 public void saveOrUpdate(List<ShiftDifferentialRule> shiftDifferentialRules) {
919 shiftDifferentialRuleDao.saveOrUpdate(shiftDifferentialRules);
920 }
921
922 @Override
923 public void saveOrUpdate(ShiftDifferentialRule shiftDifferentialRule) {
924 shiftDifferentialRuleDao.saveOrUpdate(shiftDifferentialRule);
925 }
926
927 }