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