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 boolean ruleAppliedAlready = false;
309 if(CollectionUtils.isNotEmpty(b.getTimeHourDetails())) {
310 for(TimeHourDetail tbd : b.getTimeHourDetails()) {
311 if(tbd.getEarnCode().equals(rule.getEarnCode())) {
312 ruleAppliedAlready = true;
313 }
314 }
315 }
316
317 if(!ruleAppliedAlready) {
318 BigDecimal hrs = TKUtils.convertMillisToHours(blockInterval.overlap(previousDayShiftInterval).toDurationMillis());
319 hoursBeforeVirtualDay = hoursBeforeVirtualDay.add(hrs);
320 }
321 }
322
323 } else {
324
325 break;
326 }
327
328 previousBlockInterval = blockInterval;
329
330 }
331 }
332 } else {
333
334 }
335 }
336 }
337 }
338
339 BigDecimal hoursToApply = BigDecimal.ZERO;
340 BigDecimal hoursToApplyPrevious = BigDecimal.ZERO;
341
342
343
344 if (hoursBeforeVirtualDay.compareTo(rule.getMinHours()) <= 0) {
345
346 hoursToApplyPrevious = hoursBeforeVirtualDay;
347 }
348
349
350
351
352 TimeBlock previous = null;
353 List<TimeBlock> accumulatedBlocks = new ArrayList<TimeBlock>();
354 List<Interval> accumulatedBlockIntervals = new ArrayList<Interval>();
355
356 long accumulatedMillis = TKUtils.convertHoursToMillis(hoursBeforeVirtualDay);
357
358 boolean previousDayOnly = false;
359 if (!dayIsRuleActive(currentDay, rule)) {
360 if (dayIsRuleActive(currentDay.minusDays(1), rule)) {
361 previousDayOnly = true;
362 } else {
363
364 continue;
365 }
366
367 }
368
369
370
371
372
373
374
375
376
377
378
379
380 List<TimeBlock> previousBlocksFiltered = (previousPayPeriodPrevDay) ? null : filterBlocksByApplicableEarnGroup(fromEarnGroup, ruleTimeBlocksPrev);
381
382 for (TimeBlock current : ruleTimeBlocksCurr) {
383 if (!timeBlockHasEarnCode(fromEarnGroup, current)) {
384
385 continue;
386 }
387
388 Interval blockInterval = new Interval(current.getBeginDateTime().withZone(zone), current.getEndDateTime().withZone(zone));
389
390
391
392
393 if (previousDayShiftInterval.overlaps(shiftInterval)) {
394 LOG.error("Interval of greater than 24 hours created in the rules processing.");
395 return;
396
397 }
398
399
400
401
402
403 Interval overlap = previousDayShiftInterval.overlap(blockInterval);
404 evalInterval = previousDayShiftInterval;
405 boolean overlapFromPreviousDay = true;
406 if (overlap == null) {
407 if (hoursToApplyPrevious.compareTo(BigDecimal.ZERO) > 0) {
408
409
410
411
412 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
413 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
414 accumulatedMillis = 0L;
415 hoursToApply = BigDecimal.ZERO;
416 hoursToApplyPrevious = BigDecimal.ZERO;
417 }
418
419
420
421
422
423 if (previousDayOnly) {
424 continue;
425 }
426
427 overlap = shiftInterval.overlap(blockInterval);
428 evalInterval = shiftInterval;
429 overlapFromPreviousDay = false;
430 }
431
432
433
434 if (overlap != null) {
435
436 if (previous != null) {
437
438
439 Interval previousBlockInterval = new Interval(previous.getBeginDateTime().withZone(zone), previous.getEndDateTime().withZone(zone));
440 if(evalInterval.overlaps(previousBlockInterval)) {
441
442 if (rule.getMaxGap().compareTo(BigDecimal.ZERO) != 0 && exceedsMaxGap(previous, current, rule.getMaxGap())) {
443 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
444 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
445 accumulatedMillis = 0L;
446 hoursToApply = BigDecimal.ZERO;
447 hoursToApplyPrevious = BigDecimal.ZERO;
448 } else {
449
450
451 List<Interval> overlapIntervals = getOverlappingIntervals(blockInterval,rule);
452 for(Interval overlapWithShift : overlapIntervals){
453 long millis = overlapWithShift.toDurationMillis();
454 accumulatedMillis += millis;
455 hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
456 }
457 }
458 } else {
459
460
461 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
462 this.applyAccumulatedWrapper(accumHours, previousDayShiftInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
463
464 long millis = overlap.toDurationMillis();
465 accumulatedMillis = millis;
466 hoursToApply = TKUtils.convertMillisToHours(millis);
467 }
468 } else {
469
470 boolean previousDayActive = dayIsRuleActive(previousDayShiftInterval.getStart(), rule);
471 if(!previousDayActive && overlapFromPreviousDay) {
472 continue;
473 } else {
474
475 List<Interval> overlapIntervals = getOverlappingIntervals(blockInterval,rule);
476 for(Interval overlapWithShift : overlapIntervals){
477 long millis = overlapWithShift.toDurationMillis();
478 accumulatedMillis += millis;
479 hoursToApply = hoursToApply.add(TKUtils.convertMillisToHours(millis));
480 }
481 }
482 }
483 accumulatedBlocks.add(current);
484 accumulatedBlockIntervals.add(blockInterval);
485 previous = current;
486 } else {
487
488 if (previous != null) {
489 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
490 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
491 accumulatedMillis = 0L;
492 hoursToApply = BigDecimal.ZERO;
493 hoursToApplyPrevious = BigDecimal.ZERO;
494 }
495 }
496
497 }
498
499
500
501 BigDecimal accumHours = TKUtils.convertMillisToHours(accumulatedMillis);
502 this.applyAccumulatedWrapper(accumHours, evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocksFiltered, hoursToApplyPrevious, hoursToApply, rule);
503 }
504 }
505
506 jobNumberToTimeBlocksPreviousDay = jobNumberToTimeBlocks;
507 previousPayPeriodPrevDay = false;
508 }
509
510 }
511
512 @Override
513 public List<ShiftDifferentialRule> getShiftDifferentialRules(String userPrincipalId, String location, String hrSalGroup, String payGrade, LocalDate fromEffdt, LocalDate toEffdt, String active, String showHist) {
514 List<ShiftDifferentialRule> results = new ArrayList<ShiftDifferentialRule>();
515
516 List<ShiftDifferentialRule> shiftDifferentialRuleObjs = shiftDifferentialRuleDao.getShiftDifferentialRules(location, hrSalGroup, payGrade, fromEffdt, toEffdt, active, showHist);
517
518 for (ShiftDifferentialRule shiftDifferentialRuleObj : shiftDifferentialRuleObjs) {
519 Map<String, String> roleQualification = new HashMap<String, String>();
520 roleQualification.put(KimConstants.AttributeConstants.PRINCIPAL_ID, userPrincipalId);
521 roleQualification.put(KPMERoleMemberAttribute.LOCATION.getRoleMemberAttributeName(), shiftDifferentialRuleObj.getLocation());
522
523 Map<String, String> permissionDetails = new HashMap<String, String>();
524 permissionDetails.put(KimConstants.AttributeConstants.DOCUMENT_TYPE_NAME, KRADServiceLocatorWeb.getDocumentDictionaryService().getMaintenanceDocumentTypeName(ShiftDifferentialRule.class));
525
526 if (!KimApiServiceLocator.getPermissionService().isPermissionDefinedByTemplate(KPMENamespace.KPME_WKFLW.getNamespaceCode(),
527 KPMEPermissionTemplate.VIEW_KPME_RECORD.getPermissionTemplateName(), permissionDetails)
528 || KimApiServiceLocator.getPermissionService().isAuthorizedByTemplate(userPrincipalId, KPMENamespace.KPME_WKFLW.getNamespaceCode(),
529 KPMEPermissionTemplate.VIEW_KPME_RECORD.getPermissionTemplateName(), permissionDetails, roleQualification)) {
530 results.add(shiftDifferentialRuleObj);
531 }
532 }
533
534 return results;
535 }
536
537 private List<TimeBlock> filterBlocksByApplicableEarnGroup(Set<String> fromEarnGroup, List<TimeBlock> blocks) {
538 List<TimeBlock> filtered;
539
540 if (blocks == null || blocks.size() == 0)
541 filtered = null;
542 else {
543 filtered = new ArrayList<TimeBlock>();
544 for (TimeBlock b : blocks) {
545 if (timeBlockHasEarnCode(fromEarnGroup, b))
546 filtered.add(b);
547 }
548 }
549
550 return filtered;
551 }
552
553 private void applyAccumulatedWrapper(BigDecimal accumHours,
554 Interval evalInterval,
555 List<Interval>accumulatedBlockIntervals,
556 List<TimeBlock>accumulatedBlocks,
557 List<TimeBlock> previousBlocks,
558 BigDecimal hoursToApplyPrevious,
559 BigDecimal hoursToApply,
560 ShiftDifferentialRule rule) {
561 if (accumHours.compareTo(rule.getMinHours()) >= 0) {
562 this.applyPremium(evalInterval, accumulatedBlockIntervals, accumulatedBlocks, previousBlocks, hoursToApplyPrevious, hoursToApply, rule.getEarnCode(), rule);
563 }
564 accumulatedBlocks.clear();
565 accumulatedBlockIntervals.clear();
566 }
567
568 private void sortTimeBlocksInverse(List<TimeBlock> blocks) {
569 Collections.sort(blocks, new Comparator<TimeBlock>() {
570 public int compare(TimeBlock tb1, TimeBlock tb2) {
571 if (tb1 != null && tb2 != null)
572 return -1 * tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
573 return 0;
574 }
575 });
576 }
577
578 private void sortTimeBlocksNatural(List<TimeBlock> blocks) {
579 Collections.sort(blocks, new Comparator<TimeBlock>() {
580 public int compare(TimeBlock tb1, TimeBlock tb2) {
581 if (tb1 != null && tb2 != null)
582 return tb1.getBeginTimestamp().compareTo(tb2.getBeginTimestamp());
583 return 0;
584 }
585 });
586 }
587
588
589
590
591
592
593
594
595
596
597
598 protected void applyPremium(Interval shift, List<Interval> blockIntervals, List<TimeBlock> blocks, List<TimeBlock> previousBlocks, BigDecimal initialHours, BigDecimal hours, String earnCode, ShiftDifferentialRule rule) {
599 for (int i=0; i<blocks.size(); i++) {
600 TimeBlock b = blocks.get(i);
601
602
603 if (i == 0 && (initialHours.compareTo(BigDecimal.ZERO) > 0)) {
604
605
606 if (previousBlocks != null && previousBlocks.size() > 0 && previousBlocks.get(0).getDocumentId().equals(b.getDocumentId())) {
607 for (TimeBlock pb : previousBlocks) {
608 BigDecimal lunchSub = this.negativeTimeHourDetailSum(pb);
609 initialHours = BigDecimal.ZERO.max(initialHours.add(lunchSub));
610 if (initialHours.compareTo(BigDecimal.ZERO) <= 0)
611 break;
612
613
614 BigDecimal hoursToApply = initialHours.min(pb.getHours().add(lunchSub));
615 addPremiumTimeHourDetail(pb, hoursToApply, earnCode);
616 initialHours = initialHours.subtract(hoursToApply, HrConstants.MATH_CONTEXT);
617 if (initialHours.compareTo(BigDecimal.ZERO) <= 0)
618 break;
619 }
620 } else {
621 addPremiumTimeHourDetail(b, initialHours, earnCode);
622 }
623 }
624
625 BigDecimal lunchSub = this.negativeTimeHourDetailSum(b);
626 hours = BigDecimal.ZERO.max(hours.add(lunchSub));
627
628 if (hours.compareTo(BigDecimal.ZERO) > 0) {
629 Interval blockInterval = blockIntervals.get(i);
630 List<Interval> overlapIntervals = getOverlappingIntervals(blockInterval, rule);
631 for (Interval overlapInterval : overlapIntervals) {
632 if (overlapInterval == null) {
633 continue;
634 }
635
636 long overlap = overlapInterval.toDurationMillis();
637 BigDecimal hoursMax = TKUtils.convertMillisToHours(overlap);
638
639
640 BigDecimal hoursToApply = hours.min(hoursMax.add(lunchSub));
641
642 addPremiumTimeHourDetail(b, hoursToApply, earnCode);
643 hours = hours.subtract(hoursToApply, HrConstants.MATH_CONTEXT);
644 }
645 }
646 }
647 }
648
649
650
651
652
653
654
655
656
657
658
659 List<Interval> getOverlappingIntervals(Interval timeBlockOverlap, ShiftDifferentialRule rule){
660 List<Interval> overlappingIntervals = new ArrayList<Interval>();
661
662 DateTimeZone zone = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
663
664
665 DateTime previousDay = timeBlockOverlap.getStart().minusDays(1);
666 if(dayIsRuleActive(previousDay,rule)){
667 LocalTime ruleStart = new LocalTime(rule.getBeginTime(), zone);
668 LocalTime ruleEnd = new LocalTime(rule.getEndTime(), zone);
669
670 DateTime shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getStart());
671 if (ruleEnd.isAfter(ruleStart)) {
672 shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getEnd().minusDays(1));
673 }
674 DateTime shiftStart = ruleStart.toDateTime(previousDay);
675 Interval shiftInterval = new Interval(shiftStart,shiftEnd);
676 Interval overlapInterval = shiftInterval.overlap(timeBlockOverlap);
677 if(overlapInterval != null){
678 overlappingIntervals.add(overlapInterval);
679 }
680 }
681
682 DateTime currentDay = timeBlockOverlap.getStart();
683 if(dayIsRuleActive(currentDay,rule)){
684 LocalTime ruleStart = new LocalTime(rule.getBeginTime(), zone);
685 LocalTime ruleEnd = new LocalTime(rule.getEndTime(), zone);
686
687 DateTime shiftEnd = null;
688 if(ruleEnd.isBefore(ruleStart) || ruleEnd.isEqual(ruleStart)){
689 shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getEnd().plusDays(1));
690 } else {
691 shiftEnd = ruleEnd.toDateTime(timeBlockOverlap.getEnd());
692 }
693 DateTime shiftStart = ruleStart.toDateTime(currentDay);
694 Interval shiftInterval = new Interval(shiftStart,shiftEnd);
695 Interval overlapInterval = shiftInterval.overlap(timeBlockOverlap);
696 if(overlapInterval != null){
697 overlappingIntervals.add(overlapInterval);
698 }
699 }
700
701 return overlappingIntervals;
702
703 }
704
705 void addPremiumTimeHourDetail(TimeBlock block, BigDecimal hours, String earnCode) {
706 List<TimeHourDetail> details = block.getTimeHourDetails();
707 TimeHourDetail premium = new TimeHourDetail();
708 premium.setHours(hours);
709 premium.setEarnCode(earnCode);
710 premium.setTkTimeBlockId(block.getTkTimeBlockId());
711 details.add(premium);
712 }
713
714
715
716
717
718
719
720
721
722
723 boolean exceedsMaxGap(TimeBlock previous, TimeBlock current, BigDecimal maxGap) {
724 long difference = current.getBeginTimestamp().getTime() - previous.getEndTimestamp().getTime();
725 BigDecimal gapMinutes = TKUtils.convertMillisToMinutes(difference);
726
727 return (gapMinutes.compareTo(maxGap) > 0);
728 }
729
730 public void setShiftDifferentialRuleDao(ShiftDifferentialRuleDao shiftDifferentialRuleDao) {
731 this.shiftDifferentialRuleDao = shiftDifferentialRuleDao;
732 }
733
734 @Override
735 public ShiftDifferentialRule getShiftDifferentialRule(String tkShiftDifferentialRuleId) {
736 return this.shiftDifferentialRuleDao.findShiftDifferentialRule(tkShiftDifferentialRuleId);
737 }
738
739 @Override
740 public List<ShiftDifferentialRule> getShiftDifferentalRules(String location, String hrSalGroup, String payGrade, String pyCalendarGroup, LocalDate asOfDate) {
741 List<ShiftDifferentialRule> sdrs = new ArrayList<ShiftDifferentialRule>();
742
743
744 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, payGrade, pyCalendarGroup, asOfDate));
745
746
747 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, "%", pyCalendarGroup, asOfDate));
748
749
750 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", payGrade, pyCalendarGroup, asOfDate));
751
752
753 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", "%", pyCalendarGroup, asOfDate));
754
755
756 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, payGrade, pyCalendarGroup, asOfDate));
757
758
759 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, "%", pyCalendarGroup, asOfDate));
760
761
762 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", payGrade, pyCalendarGroup, asOfDate));
763
764
765 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", "%", pyCalendarGroup, asOfDate));
766
767
768 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, payGrade, "%", asOfDate));
769
770
771 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, hrSalGroup, "%", "%", asOfDate));
772
773
774 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", payGrade, "%", asOfDate));
775
776
777 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules(location, "%", "%", "%", asOfDate));
778
779
780 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, payGrade, "%", asOfDate));
781
782
783 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", hrSalGroup, "%", "%", asOfDate));
784
785
786 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", payGrade, "%", asOfDate));
787
788
789 sdrs.addAll(shiftDifferentialRuleDao.findShiftDifferentialRules("%", "%", "%", "%", asOfDate));
790
791 return sdrs;
792 }
793
794 private boolean dayIsRuleActive(DateTime currentDate, ShiftDifferentialRule sdr) {
795 boolean active = false;
796
797 switch (currentDate.getDayOfWeek()) {
798 case DateTimeConstants.MONDAY:
799 active = sdr.isMonday();
800 break;
801 case DateTimeConstants.TUESDAY:
802 active = sdr.isTuesday();
803 break;
804 case DateTimeConstants.WEDNESDAY:
805 active = sdr.isWednesday();
806 break;
807 case DateTimeConstants.THURSDAY:
808 active = sdr.isThursday();
809 break;
810 case DateTimeConstants.FRIDAY:
811 active = sdr.isFriday();
812 break;
813 case DateTimeConstants.SATURDAY:
814 active = sdr.isSaturday();
815 break;
816 case DateTimeConstants.SUNDAY:
817 active = sdr.isSunday();
818 break;
819 }
820
821 return active;
822 }
823
824 @Override
825 public void saveOrUpdate(List<ShiftDifferentialRule> shiftDifferentialRules) {
826 shiftDifferentialRuleDao.saveOrUpdate(shiftDifferentialRules);
827 }
828
829 @Override
830 public void saveOrUpdate(ShiftDifferentialRule shiftDifferentialRule) {
831 shiftDifferentialRuleDao.saveOrUpdate(shiftDifferentialRule);
832 }
833
834 }