1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.kpme.tklm.time.rules.overtime.weekly.service;
17
18 import java.math.BigDecimal;
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Comparator;
23 import java.util.List;
24 import java.util.ListIterator;
25 import java.util.Set;
26
27 import org.apache.commons.collections.CollectionUtils;
28 import org.apache.commons.lang.ObjectUtils;
29 import org.apache.commons.lang.StringUtils;
30 import org.joda.time.DateTime;
31 import org.joda.time.DateTimeZone;
32 import org.joda.time.LocalDate;
33 import org.kuali.kpme.core.assignment.Assignment;
34 import org.kuali.kpme.core.calendar.entry.CalendarEntry;
35 import org.kuali.kpme.core.earncode.EarnCode;
36 import org.kuali.kpme.core.service.HrServiceLocator;
37 import org.kuali.kpme.core.util.HrConstants;
38 import org.kuali.kpme.core.workarea.WorkArea;
39 import org.kuali.kpme.tklm.leave.block.LeaveBlock;
40 import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
41 import org.kuali.kpme.tklm.time.flsa.FlsaDay;
42 import org.kuali.kpme.tklm.time.flsa.FlsaWeek;
43 import org.kuali.kpme.tklm.time.rules.overtime.weekly.WeeklyOvertimeRule;
44 import org.kuali.kpme.tklm.time.rules.overtime.weekly.dao.WeeklyOvertimeRuleDao;
45 import org.kuali.kpme.tklm.time.service.TkServiceLocator;
46 import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
47 import org.kuali.kpme.tklm.time.timehourdetail.TimeHourDetail;
48 import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
49
50 import org.kuali.kpme.tklm.time.util.TkTimeBlockAggregate;
51 import org.kuali.kpme.tklm.time.workflow.TimesheetDocumentHeader;
52 import org.kuali.rice.krad.service.KRADServiceLocator;
53
54 public class WeeklyOvertimeRuleServiceImpl implements WeeklyOvertimeRuleService {
55
56 private WeeklyOvertimeRuleDao weeklyOvertimeRuleDao;
57
58 @Override
59 public void processWeeklyOvertimeRule(TimesheetDocument timesheetDocument, TkTimeBlockAggregate aggregate) {
60
61 LocalDate asOfDate = timesheetDocument.getDocumentHeader().getEndDateTime().toLocalDate();
62 String principalId = timesheetDocument.getDocumentHeader().getPrincipalId();
63 DateTime beginDate = timesheetDocument.getDocumentHeader().getBeginDateTime();
64 DateTime endDate = timesheetDocument.getDocumentHeader().getEndDateTime();
65 List<WeeklyOvertimeRule> weeklyOvertimeRules = getWeeklyOvertimeRules(asOfDate);
66
67 List<List<FlsaWeek>> flsaWeeks = getFlsaWeeks(principalId, beginDate, endDate, aggregate);
68
69 for (WeeklyOvertimeRule weeklyOvertimeRule : weeklyOvertimeRules) {
70 Set<String> maxHoursEarnCodes = HrServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(weeklyOvertimeRule.getMaxHoursEarnGroup(), asOfDate);
71 Set<String> convertFromEarnCodes = HrServiceLocator.getEarnCodeGroupService().getEarnCodeListForEarnCodeGroup(weeklyOvertimeRule.getConvertFromEarnGroup(), asOfDate);
72
73 for (List<FlsaWeek> flsaWeekParts : flsaWeeks) {
74 BigDecimal existingMaxHours = getMaxHours(flsaWeekParts, maxHoursEarnCodes);
75 BigDecimal newOvertimeHours = existingMaxHours.subtract(weeklyOvertimeRule.getMaxHours(), HrConstants.MATH_CONTEXT);
76
77 if (newOvertimeHours.compareTo(BigDecimal.ZERO) != 0) {
78 applyOvertimeToFlsaWeeks(flsaWeekParts, weeklyOvertimeRule, asOfDate, convertFromEarnCodes, newOvertimeHours);
79 }
80 }
81 }
82
83 savePreviousNextCalendarTimeBlocks(flsaWeeks);
84 }
85
86
87
88
89
90
91
92
93
94
95
96
97 protected List<List<FlsaWeek>> getFlsaWeeks(String principalId, DateTime beginDate, DateTime endDate, TkTimeBlockAggregate aggregate) {
98 List<List<FlsaWeek>> flsaWeeks = new ArrayList<List<FlsaWeek>>();
99
100 DateTimeZone zone = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
101 List<FlsaWeek> currentWeeks = aggregate.getFlsaWeeks(zone);
102
103 for (ListIterator<FlsaWeek> weekIterator = currentWeeks.listIterator(); weekIterator.hasNext(); ) {
104 List<FlsaWeek> flsaWeek = new ArrayList<FlsaWeek>();
105
106 int index = weekIterator.nextIndex();
107 FlsaWeek currentWeek = weekIterator.next();
108
109 if (index == 0 && !currentWeek.isFirstWeekFull()) {
110 TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, beginDate);
111 if (timesheetDocumentHeader != null) {
112 TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentHeader.getDocumentId());
113 List<String> assignmentKeys = new ArrayList<String>();
114 for(Assignment assignment : timesheetDocument.getAssignments()) {
115 assignmentKeys.add(assignment.getAssignmentKey());
116 }
117
118 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
119 List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, timesheetDocumentHeader.getBeginDateTime().toLocalDate(), timesheetDocumentHeader.getEndDateTime().toLocalDate(), assignmentKeys);
120 if (CollectionUtils.isNotEmpty(timeBlocks)) {
121 CalendarEntry calendarEntry = HrServiceLocator.getCalendarEntryService().getCalendarDatesByPayEndDate(principalId, timesheetDocumentHeader.getEndDateTime(), HrConstants.PAY_CALENDAR_TYPE);
122 TkTimeBlockAggregate previousAggregate = new TkTimeBlockAggregate(timeBlocks, leaveBlocks, calendarEntry, calendarEntry.getCalendarObj(), true);
123 List<FlsaWeek> previousWeek = previousAggregate.getFlsaWeeks(zone);
124 if (CollectionUtils.isNotEmpty(previousWeek)) {
125 flsaWeek.add(previousWeek.get(previousWeek.size() - 1));
126 }
127 }
128 }
129 }
130
131 flsaWeek.add(currentWeek);
132
133 if (index == currentWeeks.size() - 1 && !currentWeek.isLastWeekFull()) {
134 TimesheetDocumentHeader timesheetDocumentHeader = TkServiceLocator.getTimesheetDocumentHeaderService().getNextDocumentHeader(principalId, endDate);
135 if (timesheetDocumentHeader != null) {
136 TimesheetDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocumentHeader.getDocumentId());
137 List<String> assignmentKeys = new ArrayList<String>();
138 for(Assignment assignment : timesheetDocument.getAssignments()) {
139 assignmentKeys.add(assignment.getAssignmentKey());
140 }
141 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(timesheetDocumentHeader.getDocumentId());
142 List<LeaveBlock> leaveBlocks = LmServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(principalId, timesheetDocumentHeader.getBeginDateTime().toLocalDate(), timesheetDocumentHeader.getEndDateTime().toLocalDate(), assignmentKeys);
143 if (CollectionUtils.isNotEmpty(timeBlocks)) {
144 CalendarEntry calendarEntry = HrServiceLocator.getCalendarEntryService().getCalendarDatesByPayEndDate(principalId, timesheetDocumentHeader.getEndDateTime(), HrConstants.PAY_CALENDAR_TYPE);
145 TkTimeBlockAggregate nextAggregate = new TkTimeBlockAggregate(timeBlocks, leaveBlocks, calendarEntry, calendarEntry.getCalendarObj(), true);
146 List<FlsaWeek> nextWeek = nextAggregate.getFlsaWeeks(zone);
147 if (CollectionUtils.isNotEmpty(nextWeek)) {
148 flsaWeek.add(nextWeek.get(0));
149 }
150 }
151 }
152 }
153
154 flsaWeeks.add(flsaWeek);
155 }
156
157 return flsaWeeks;
158 }
159
160
161
162
163
164
165
166
167
168 protected BigDecimal getMaxHours(List<FlsaWeek> flsaWeeks, Set<String> maxHoursEarnCodes) {
169 BigDecimal maxHours = BigDecimal.ZERO;
170
171 for (FlsaWeek flsaWeek : flsaWeeks) {
172 for (FlsaDay flsaDay : flsaWeek.getFlsaDays()) {
173 for (TimeBlock timeBlock : flsaDay.getAppliedTimeBlocks()) {
174 for (TimeHourDetail timeHourDetail : timeBlock.getTimeHourDetails()) {
175 if (maxHoursEarnCodes.contains(timeHourDetail.getEarnCode())) {
176 maxHours = maxHours.add(timeHourDetail.getHours(), HrConstants.MATH_CONTEXT);
177 }
178 }
179 }
180 for (LeaveBlock leaveBlock : flsaDay.getAppliedLeaveBlocks()) {
181 if (maxHoursEarnCodes.contains(leaveBlock.getEarnCode())) {
182 maxHours = maxHours.add(leaveBlock.getLeaveAmount().negate());
183 }
184 }
185 }
186 }
187
188 return maxHours;
189 }
190
191
192
193
194
195
196
197
198
199
200
201 protected String getOvertimeEarnCode(WeeklyOvertimeRule weeklyOvertimeRule, TimeBlock timeBlock, LocalDate asOfDate) {
202 String overtimeEarnCode = weeklyOvertimeRule.getConvertToEarnCode();
203
204
205 WorkArea workArea = HrServiceLocator.getWorkAreaService().getWorkArea(timeBlock.getWorkArea(), timeBlock.getEndDateTime().toLocalDate());
206 if (StringUtils.isNotBlank(workArea.getDefaultOvertimeEarnCode())){
207 overtimeEarnCode = workArea.getDefaultOvertimeEarnCode();
208 }
209
210 if (StringUtils.isNotEmpty(timeBlock.getOvertimePref())) {
211 overtimeEarnCode = timeBlock.getOvertimePref();
212 }
213
214 return overtimeEarnCode;
215 }
216
217
218
219
220
221
222
223
224
225
226 protected void applyOvertimeToFlsaWeeks(List<FlsaWeek> flsaWeeks, WeeklyOvertimeRule weeklyOvertimeRule, LocalDate asOfDate, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
227 List<FlsaDay> flsaDays = getFlsaDays(flsaWeeks);
228
229 if (overtimeHours.compareTo(BigDecimal.ZERO) > 0) {
230 applyPositiveOvertimeToFlsaWeek(flsaDays, weeklyOvertimeRule, asOfDate, convertFromEarnCodes, overtimeHours);
231 } else if (overtimeHours.compareTo(BigDecimal.ZERO) < 0) {
232 applyNegativeOvertimeToFlsaWeek(flsaDays, weeklyOvertimeRule, asOfDate, convertFromEarnCodes, overtimeHours);
233 }
234
235 removeEmptyOvertime(flsaDays, weeklyOvertimeRule, asOfDate);
236 }
237
238
239
240
241
242
243
244
245 protected List<FlsaDay> getFlsaDays(List<FlsaWeek> flsaWeeks) {
246 List<FlsaDay> flsaDays = new ArrayList<FlsaDay>();
247
248 for (FlsaWeek flsaWeek : flsaWeeks) {
249 flsaDays.addAll(flsaWeek.getFlsaDays());
250 }
251
252 return flsaDays;
253 }
254
255
256
257
258
259
260
261
262
263
264 protected void applyPositiveOvertimeToFlsaWeek(List<FlsaDay> flsaDays, WeeklyOvertimeRule weeklyOvertimeRule, LocalDate asOfDate, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
265 for (ListIterator<FlsaDay> dayIterator = flsaDays.listIterator(flsaDays.size()); dayIterator.hasPrevious(); ) {
266 FlsaDay flsaDay = dayIterator.previous();
267
268 List<TimeBlock> timeBlocks = flsaDay.getAppliedTimeBlocks();
269 Collections.sort(timeBlocks, new Comparator<TimeBlock>() {
270 public int compare(TimeBlock timeBlock1, TimeBlock timeBlock2) {
271 return ObjectUtils.compare(timeBlock1.getBeginTimestamp(), timeBlock2.getBeginTimestamp());
272 }
273 });
274
275 for (ListIterator<TimeBlock> timeBlockIterator = timeBlocks.listIterator(timeBlocks.size()); timeBlockIterator.hasPrevious(); ) {
276 TimeBlock timeBlock = timeBlockIterator.previous();
277 String overtimeEarnCode = getOvertimeEarnCode(weeklyOvertimeRule, timeBlock, asOfDate);
278 overtimeHours = applyPositiveOvertimeOnTimeBlock(timeBlock, overtimeEarnCode, convertFromEarnCodes, overtimeHours);
279 }
280
281 flsaDay.remapTimeHourDetails();
282 }
283 }
284
285
286
287
288
289
290
291
292
293
294 protected void applyNegativeOvertimeToFlsaWeek(List<FlsaDay> flsaDays, WeeklyOvertimeRule weeklyOvertimeRule, LocalDate asOfDate, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
295 for (ListIterator<FlsaDay> dayIterator = flsaDays.listIterator(); dayIterator.hasNext(); ) {
296 FlsaDay flsaDay = dayIterator.next();
297
298 List<TimeBlock> timeBlocks = flsaDay.getAppliedTimeBlocks();
299 Collections.sort(timeBlocks, new Comparator<TimeBlock>() {
300 public int compare(TimeBlock timeBlock1, TimeBlock timeBlock2) {
301 return ObjectUtils.compare(timeBlock1.getBeginTimestamp(), timeBlock2.getBeginTimestamp());
302 }
303 });
304
305 for (ListIterator<TimeBlock> timeBlockIterator = timeBlocks.listIterator(); timeBlockIterator.hasNext(); ) {
306 TimeBlock timeBlock = timeBlockIterator.next();
307 String overtimeEarnCode = getOvertimeEarnCode(weeklyOvertimeRule, timeBlock, asOfDate);
308 overtimeHours = applyNegativeOvertimeOnTimeBlock(timeBlock, overtimeEarnCode, convertFromEarnCodes, overtimeHours);
309 }
310
311 flsaDay.remapTimeHourDetails();
312 }
313 }
314
315 protected void removeEmptyOvertime(List<FlsaDay> flsaDays, WeeklyOvertimeRule weeklyOvertimeRule, LocalDate asOfDate) {
316 for (ListIterator<FlsaDay> dayIterator = flsaDays.listIterator(); dayIterator.hasNext(); ) {
317 FlsaDay flsaDay = dayIterator.next();
318
319 List<TimeBlock> timeBlocks = flsaDay.getAppliedTimeBlocks();
320 for (ListIterator<TimeBlock> timeBlockIterator = timeBlocks.listIterator(); timeBlockIterator.hasNext(); ) {
321 TimeBlock timeBlock = timeBlockIterator.next();
322 String overtimeEarnCode = getOvertimeEarnCode(weeklyOvertimeRule, timeBlock, asOfDate);
323
324 List<TimeHourDetail> timeHourDetails = timeBlock.getTimeHourDetails();
325 List<TimeHourDetail> oldTimeHourDetails = new ArrayList<TimeHourDetail>();
326
327 TimeHourDetail overtimeTimeHourDetail = getTimeHourDetailByEarnCode(timeHourDetails, Collections.singletonList(overtimeEarnCode));
328 if (overtimeTimeHourDetail != null) {
329 if (overtimeTimeHourDetail.getHours().compareTo(BigDecimal.ZERO) == 0) {
330 oldTimeHourDetails.add(overtimeTimeHourDetail);
331 }
332 }
333
334 for (TimeHourDetail oldTimeHourDetail : oldTimeHourDetails) {
335 timeBlock.removeTimeHourDetail(oldTimeHourDetail);
336 }
337 }
338 }
339 }
340
341
342
343
344
345
346
347
348
349
350
351 protected BigDecimal applyPositiveOvertimeOnTimeBlock(TimeBlock timeBlock, String overtimeEarnCode, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
352 BigDecimal applied = BigDecimal.ZERO;
353 List<TimeHourDetail> timeHourDetails = timeBlock.getTimeHourDetails();
354 List<TimeHourDetail> newTimeHourDetails = new ArrayList<TimeHourDetail>();
355
356 for (TimeHourDetail timeHourDetail : timeHourDetails) {
357 if (convertFromEarnCodes.contains(timeHourDetail.getEarnCode())) {
358 if (timeHourDetail.getHours().compareTo(overtimeHours) >= 0) {
359 applied = overtimeHours;
360 } else {
361 applied = timeHourDetail.getHours();
362 }
363
364 EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(overtimeEarnCode, timeBlock.getEndDateTime().toLocalDate());
365 BigDecimal hours = earnCodeObj.getInflateFactor().multiply(applied, HrConstants.MATH_CONTEXT).setScale(HrConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_UP);
366
367 TimeHourDetail overtimeTimeHourDetail = getTimeHourDetailByEarnCode(timeHourDetails, Collections.singletonList(overtimeEarnCode));
368 if (overtimeTimeHourDetail != null) {
369 overtimeTimeHourDetail.setHours(overtimeTimeHourDetail.getHours().add(hours, HrConstants.MATH_CONTEXT));
370 } else {
371 TimeHourDetail newTimeHourDetail = new TimeHourDetail();
372 newTimeHourDetail.setTkTimeBlockId(timeBlock.getTkTimeBlockId());
373 newTimeHourDetail.setEarnCode(overtimeEarnCode);
374 newTimeHourDetail.setHours(hours);
375
376 newTimeHourDetails.add(newTimeHourDetail);
377 }
378
379 timeHourDetail.setHours(timeHourDetail.getHours().subtract(applied, HrConstants.MATH_CONTEXT).setScale(HrConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_UP));
380 }
381 }
382
383 for (TimeHourDetail newTimeHourDetail : newTimeHourDetails) {
384 timeBlock.addTimeHourDetail(newTimeHourDetail);
385 }
386
387 return overtimeHours.subtract(applied);
388 }
389
390
391
392
393
394
395
396
397
398
399
400 protected BigDecimal applyNegativeOvertimeOnTimeBlock(TimeBlock timeBlock, String overtimeEarnCode, Set<String> convertFromEarnCodes, BigDecimal overtimeHours) {
401 BigDecimal applied = BigDecimal.ZERO;
402 List<TimeHourDetail> timeHourDetails = timeBlock.getTimeHourDetails();
403
404 for (TimeHourDetail timeHourDetail : timeHourDetails) {
405 if (convertFromEarnCodes.contains(timeHourDetail.getEarnCode())) {
406 TimeHourDetail overtimeTimeHourDetail = getTimeHourDetailByEarnCode(timeHourDetails, Collections.singletonList(overtimeEarnCode));
407
408 if (overtimeTimeHourDetail != null) {
409 applied = overtimeTimeHourDetail.getHours().add(overtimeHours, HrConstants.MATH_CONTEXT);
410 if (applied.compareTo(BigDecimal.ZERO) >= 0) {
411 applied = overtimeHours;
412 } else {
413 applied = overtimeTimeHourDetail.getHours().negate();
414 }
415
416 EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(overtimeEarnCode, timeBlock.getEndDateTime().toLocalDate());
417 BigDecimal hours = earnCodeObj.getInflateFactor().multiply(applied, HrConstants.MATH_CONTEXT).setScale(HrConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_DOWN);
418
419 overtimeTimeHourDetail.setHours(overtimeTimeHourDetail.getHours().add(hours, HrConstants.MATH_CONTEXT));
420
421 timeHourDetail.setHours(timeHourDetail.getHours().subtract(applied, HrConstants.MATH_CONTEXT).setScale(HrConstants.BIG_DECIMAL_SCALE, BigDecimal.ROUND_HALF_UP));
422 }
423 }
424 }
425
426 return overtimeHours.subtract(applied);
427 }
428
429 protected TimeHourDetail getTimeHourDetailByEarnCode(List<TimeHourDetail> timeHourDetails, Collection<String> earnCodes) {
430 TimeHourDetail result = null;
431
432 for (TimeHourDetail timeHourDetail : timeHourDetails) {
433 if (earnCodes.contains(timeHourDetail.getEarnCode())) {
434 result = timeHourDetail;
435 break;
436 }
437 }
438
439 return result;
440 }
441
442
443
444
445
446
447 protected void savePreviousNextCalendarTimeBlocks(List<List<FlsaWeek>> flsaWeeks) {
448 for (ListIterator<List<FlsaWeek>> weeksIterator = flsaWeeks.listIterator(); weeksIterator.hasNext(); ) {
449 int index = weeksIterator.nextIndex();
450 List<FlsaWeek> currentWeekParts = weeksIterator.next();
451
452 if (index == 0 && currentWeekParts.size() > 1) {
453 FlsaWeek previousFlsaWeek = currentWeekParts.get(0);
454 for (FlsaDay flsaDay : previousFlsaWeek.getFlsaDays()) {
455 KRADServiceLocator.getBusinessObjectService().save(flsaDay.getAppliedTimeBlocks());
456 }
457 }
458
459 if (index == flsaWeeks.size() - 1 && currentWeekParts.size() > 1) {
460 FlsaWeek nextFlsaWeek = currentWeekParts.get(currentWeekParts.size() - 1);
461 for (FlsaDay flsaDay : nextFlsaWeek.getFlsaDays()) {
462 KRADServiceLocator.getBusinessObjectService().save(flsaDay.getAppliedTimeBlocks());
463 }
464 }
465 }
466 }
467
468 @Override
469 public List<WeeklyOvertimeRule> getWeeklyOvertimeRules(LocalDate asOfDate) {
470 return weeklyOvertimeRuleDao.findWeeklyOvertimeRules(asOfDate);
471 }
472
473 @Override
474 public void saveOrUpdate(WeeklyOvertimeRule weeklyOvertimeRule) {
475 weeklyOvertimeRuleDao.saveOrUpdate(weeklyOvertimeRule);
476 }
477
478 @Override
479 public void saveOrUpdate(List<WeeklyOvertimeRule> weeklyOvertimeRules) {
480 weeklyOvertimeRuleDao.saveOrUpdate(weeklyOvertimeRules);
481 }
482
483 public void setWeeklyOvertimeRuleDao(WeeklyOvertimeRuleDao weeklyOvertimeRuleDao) {
484 this.weeklyOvertimeRuleDao = weeklyOvertimeRuleDao;
485 }
486
487 @Override
488 public WeeklyOvertimeRule getWeeklyOvertimeRule(String tkWeeklyOvertimeRuleId) {
489 return weeklyOvertimeRuleDao.getWeeklyOvertimeRule(tkWeeklyOvertimeRuleId);
490 }
491
492 }