1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.kpme.tklm.time.detail.validation;
17
18 import java.math.BigDecimal;
19 import java.util.ArrayList;
20 import java.util.List;
21
22 import org.apache.commons.collections.CollectionUtils;
23 import org.apache.commons.lang.StringUtils;
24 import org.joda.time.*;
25 import org.kuali.kpme.core.assignment.Assignment;
26 import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
27 import org.kuali.kpme.core.calendar.entry.CalendarEntry;
28 import org.kuali.kpme.core.earncode.EarnCode;
29 import org.kuali.kpme.core.job.Job;
30 import org.kuali.kpme.core.service.HrServiceLocator;
31 import org.kuali.kpme.core.util.HrConstants;
32 import org.kuali.kpme.core.util.HrContext;
33 import org.kuali.kpme.core.util.TKUtils;
34 import org.kuali.kpme.core.util.ValidationUtils;
35 import org.kuali.kpme.tklm.common.CalendarValidationUtil;
36 import org.kuali.kpme.tklm.common.TkConstants;
37 import org.kuali.kpme.tklm.leave.block.LeaveBlock;
38 import org.kuali.kpme.tklm.leave.calendar.validation.LeaveCalendarValidationUtil;
39 import org.kuali.kpme.tklm.leave.service.LmServiceLocator;
40 import org.kuali.kpme.tklm.leave.summary.LeaveSummary;
41 import org.kuali.kpme.tklm.time.clocklog.ClockLog;
42 import org.kuali.kpme.tklm.time.detail.web.TimeDetailActionFormBase;
43 import org.kuali.kpme.tklm.time.service.TkServiceLocator;
44 import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
45 import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
46 import org.kuali.rice.krad.util.ObjectUtils;
47
48 public class TimeDetailValidationUtil extends CalendarValidationUtil {
49
50 public static List<String> validateLeaveEntry(TimeDetailActionFormBase tdaf) throws Exception {
51
52
53 List<String> errorMsgList = new ArrayList<String>();
54 CalendarEntry payCalendarEntry = tdaf.getCalendarEntry();
55 if(ObjectUtils.isNotNull(payCalendarEntry)) {
56 LeaveBlock lb = null;
57 if(StringUtils.isNotEmpty(tdaf.getLmLeaveBlockId())) {
58 lb = LmServiceLocator.getLeaveBlockService().getLeaveBlock(tdaf.getLmLeaveBlockId());
59 }
60 errorMsgList.addAll(CalendarValidationUtil.validateEarnCode(tdaf.getSelectedEarnCode(),tdaf.getStartDate(),tdaf.getEndDate()));
61 if(errorMsgList.isEmpty()) {
62 LeaveSummary ls = LmServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDate(HrContext.getTargetPrincipalId(), TKUtils.formatDateString(tdaf.getEndDate()));
63
64 BigDecimal leaveAmount = tdaf.getLeaveAmount();
65 if(leaveAmount == null) {
66 Long startTime = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getStartDate(), tdaf.getStartTime()).getMillis();
67 Long endTime = TKUtils.convertDateStringToDateTimeWithoutZone(tdaf.getEndDate(), tdaf.getEndTime()).getMillis();
68 leaveAmount = TKUtils.getHoursBetween(startTime, endTime);
69 }
70
71
72
73
74
75
76
77
78 errorMsgList.addAll(TimeDetailValidationUtil.validateLeaveParametersByEarnCodeRecordMethod(tdaf));
79 errorMsgList.addAll(LeaveCalendarValidationUtil.validateAvailableLeaveBalanceForUsage(tdaf.getSelectedEarnCode(),
80 tdaf.getStartDate(), tdaf.getEndDate(), leaveAmount, lb));
81
82 errorMsgList.addAll(LeaveCalendarValidationUtil.validateLeaveAccrualRuleMaxUsage(ls, tdaf.getSelectedEarnCode(),
83 tdaf.getStartDate(), tdaf.getEndDate(), leaveAmount, lb));
84 errorMsgList.addAll(LeaveCalendarValidationUtil.validateHoursUnderTwentyFour(tdaf.getSelectedEarnCode(),
85 tdaf.getStartDate(), tdaf.getEndDate(),leaveAmount));
86 }
87 }
88 return errorMsgList;
89 }
90
91 public static List<String> validateLeaveParametersByEarnCodeRecordMethod(TimeDetailActionFormBase lcf) {
92 List<String> errors = new ArrayList<String>();
93 if (StringUtils.isNotBlank(lcf.getSelectedEarnCode()) && lcf.getCalendarEntry() != null) {
94
95
96 CalendarEntry calendarEntry = lcf.getCalendarEntry();
97 EarnCode earnCode = HrServiceLocator.getEarnCodeService().getEarnCode(lcf.getSelectedEarnCode(), calendarEntry.getEndPeriodFullDateTime().toLocalDate());
98 if(earnCode != null) {
99 if(earnCode.getRecordMethod().equalsIgnoreCase(HrConstants.EARN_CODE_TIME)) {
100 return LeaveCalendarValidationUtil.validateTimeParametersForLeaveEntry(earnCode, lcf.getCalendarEntry(), lcf.getStartDate(), lcf.getEndDate(), lcf.getStartTime(), lcf.getEndTime(), lcf.getSelectedAssignment(), lcf.getLmLeaveBlockId(), null);
101 }
102
103
104
105
106 else if (earnCode.getRecordMethod().equalsIgnoreCase(HrConstants.EARN_CODE_HOUR)) {
107 return validateHourParametersForLeaveEntry(earnCode, lcf.getCalendarEntry(), lcf.getStartDate(), lcf.getEndDate(), lcf.getLeaveAmount());
108 }
109 else if (earnCode.getRecordMethod().equalsIgnoreCase(HrConstants.EARN_CODE_DAY)) {
110 return validateDayParametersForLeaveEntry(earnCode, lcf.getCalendarEntry(), lcf.getStartDate(), lcf.getEndDate(), lcf.getLeaveAmount());
111 }
112 }
113 }
114 return errors;
115 }
116
117
118
119
120
121
122
123
124 @Deprecated
125 public static List<String> validateEarnCode(String earnCode, String startDateString, String endDateString) {
126 List<String> errors = new ArrayList<String>();
127
128 LocalDate tempDate = TKUtils.formatDateTimeStringNoTimezone(startDateString).toLocalDate();
129 LocalDate localEnd = TKUtils.formatDateTimeStringNoTimezone(endDateString).toLocalDate();
130
131 while(!localEnd.isBefore(tempDate)) {
132 if(!ValidationUtils.validateEarnCode(earnCode, tempDate)) {
133 errors.add("Earn Code " + earnCode + " is not available for " + tempDate);
134 break;
135 }
136 tempDate = tempDate.plusDays(1);
137 }
138
139 return errors;
140 }
141
142
143
144
145
146
147
148 public static List<String> validateTimeEntryDetails(TimeDetailActionFormBase tdaf) {
149 boolean spanningWeeks = false;
150 boolean acrossDays = false;
151
152 if(tdaf.getAcrossDays() != null) {
153 acrossDays = tdaf.getAcrossDays().equalsIgnoreCase("y");
154 }
155
156 return validateTimeEntryDetails(
157 tdaf.getHours(), tdaf.getAmount(), tdaf.getStartTime(), tdaf.getEndTime(),
158 tdaf.getStartDate(), tdaf.getEndDate(), tdaf.getTimesheetDocument(),
159 tdaf.getSelectedEarnCode(), tdaf.getSelectedAssignment(),
160 acrossDays, tdaf.getTkTimeBlockId(), tdaf.getOvertimePref()
161 );
162 }
163
164 public static List<String> validateTimeEntryDetails(BigDecimal hours, BigDecimal amount, String startTimeS, String endTimeS, String startDateS, String endDateS, TimesheetDocument timesheetDocument, String selectedEarnCode, String selectedAssignment, boolean acrossDays, String timeblockId, String overtimePref) {
165 List<String> errors = new ArrayList<String>();
166
167 if (timesheetDocument == null) {
168 errors.add("No timesheet document found.");
169 }
170 if (errors.size() > 0) return errors;
171
172 CalendarEntry payCalEntry = timesheetDocument.getCalendarEntry();
173 EarnCode earnCode = null;
174 if (StringUtils.isNotBlank(selectedEarnCode)) {
175 earnCode = HrServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, TKUtils.formatDateTimeStringNoTimezone(endDateS).toLocalDate());
176 }
177 boolean isTimeRecordMethod = earnCode != null && StringUtils.equalsIgnoreCase(earnCode.getRecordMethod(), HrConstants.EARN_CODE_TIME);
178
179 errors.addAll(CalendarValidationUtil.validateDates(startDateS, endDateS));
180
181 if (isTimeRecordMethod) {
182 errors.addAll(CalendarValidationUtil.validateTimes(startTimeS, endTimeS));
183 }
184 if (errors.size() > 0) return errors;
185
186 Long startTime;
187 Long endTime;
188
189 if (!isTimeRecordMethod) {
190 startTimeS = "0:0";
191 endTimeS = "0:0";
192 }
193
194 if (acrossDays && !endTimeS.equals("0:00")) { endDateS = startDateS;}
195
196 startTime = TKUtils.convertDateStringToDateTimeWithoutZone(startDateS, startTimeS).getMillis();
197 endTime = TKUtils.convertDateStringToDateTimeWithoutZone(endDateS, endTimeS).getMillis();
198
199 errors.addAll(CalendarValidationUtil.validateInterval(payCalEntry, startTime, endTime));
200 if (errors.size() > 0) return errors;
201
202 if (isTimeRecordMethod) {
203 if (startTimeS == null) errors.add("The start time is blank.");
204 if (endTimeS == null) errors.add("The end time is blank.");
205 if (startTime - endTime == 0) errors.add("Start time and end time cannot be equivalent");
206 }
207 if (errors.size() > 0) return errors;
208
209 DateTime startTemp = new DateTime(startTime);
210 DateTime endTemp = new DateTime(endTime);
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 AssignmentDescriptionKey assignKey = HrServiceLocator.getAssignmentService().getAssignmentDescriptionKey(selectedAssignment);
227 Assignment assign = HrServiceLocator.getAssignmentService().getAssignmentForTargetPrincipal(assignKey, startTemp.toLocalDate());
228 if (assign == null) errors.add("Assignment is not valid for start date " + TKUtils.formatDate(new LocalDate(startTime)));
229 assign = HrServiceLocator.getAssignmentService().getAssignmentForTargetPrincipal(assignKey, endTemp.toLocalDate());
230 if (assign == null) errors.add("Assignment is not valid for end date " + TKUtils.formatDate(new LocalDate(endTime)));
231 if (errors.size() > 0) return errors;
232
233
234
235
236
237
238
239
240
241
242
243 if ((startTime.compareTo(endTime) > 0 || endTime.compareTo(startTime) < 0)) {
244 errors.add("The time or date is not valid.");
245 }
246 if (errors.size() > 0) return errors;
247
248
249
250
251
252
253 if (errors.size() > 0) return errors;
254
255
256
257
258 if (acrossDays && hours == null && amount == null) {
259 if (startTemp.getHourOfDay() > endTemp.getHourOfDay()
260 && !(endTemp.getDayOfYear() - startTemp.getDayOfYear() <= 1
261 && endTemp.getHourOfDay() == 0)) {
262 errors.add("The \"apply to each day\" box should not be checked.");
263 }
264 }
265 if (errors.size() > 0) return errors;
266
267
268
269
270 if (amount != null && earnCode != null && StringUtils.equals(earnCode.getEarnCodeType(), HrConstants.EARN_CODE_AMOUNT)) {
271 if (amount.equals(BigDecimal.ZERO)) {
272 errors.add("Amount cannot be zero.");
273 }
274 if (amount.scale() > 2) {
275 errors.add("Amount cannot have more than two digits after decimal point.");
276 }
277 }
278 if (errors.size() > 0) return errors;
279
280
281
282
283
284 if (hours != null && earnCode != null && StringUtils.equals(earnCode.getEarnCodeType(), HrConstants.EARN_CODE_HOUR)) {
285 if (hours.equals(BigDecimal.ZERO)) {
286 errors.add("Hours cannot be zero.");
287 }
288 if (hours.scale() > 2) {
289 errors.add("Hours cannot have more than two digits after decimal point.");
290 }
291
292
293
294
295
296
297
298
299
300
301
302 }
303 if (errors.size() > 0) return errors;
304
305
306
307
308
309
310
311 if(hours != null && hours.compareTo(new BigDecimal(24.0)) > 0) {
312 errors.add("Hours cannot exceed 24.");
313 }
314
315
316
317
318
319 boolean isRegularEarnCode = StringUtils.equals(assign.getJob().getPayTypeObj().getRegEarnCode(),selectedEarnCode);
320 startTime = TKUtils.convertDateStringToDateTime(startDateS, startTimeS).getMillis();
321 endTime = TKUtils.convertDateStringToDateTime(endDateS, endTimeS).getMillis();
322
323 errors.addAll(validateOverlap(startTime, endTime, acrossDays, startDateS, endTimeS,startTemp, endTemp, timesheetDocument, timeblockId, isRegularEarnCode, selectedEarnCode));
324 if (errors.size() > 0) return errors;
325
326
327
328
329 return errors;
330 }
331
332 public static List<String> validateOverlap(Long startTime, Long endTime, boolean acrossDays, String startDateS, String endTimeS, DateTime startTemp, DateTime endTemp, TimesheetDocument timesheetDocument, String timeblockId, boolean isRegularEarnCode, String selectedEarnCode) {
333 List<String> errors = new ArrayList<String>();
334 Interval addedTimeblockInterval = new Interval(startTime, endTime);
335 List<Interval> dayInt = new ArrayList<Interval>();
336
337
338 ClockLog lastClockLog = TkServiceLocator.getClockLogService().getLastClockLog(HrContext.getTargetPrincipalId());
339 if(lastClockLog != null &&
340 (lastClockLog.getClockAction().equals(TkConstants.CLOCK_IN)
341 || lastClockLog.getClockAction().equals(TkConstants.LUNCH_IN))) {
342 DateTime lastClockDateTime = lastClockLog.getClockDateTime();
343
344
345
346
347
348
349
350 DateTime currentTime = DateTime.now();
351 if (lastClockDateTime.getMillis() > currentTime.getMillis()) {
352 currentTime = new DateTime(lastClockDateTime.getMillis());
353 }
354
355 Interval currentClockInInterval = new Interval(lastClockDateTime, currentTime);
356 if (isRegularEarnCode && addedTimeblockInterval.overlaps(currentClockInInterval)) {
357 errors.add("The time block you are trying to add overlaps with the current clock action.");
358 return errors;
359 }
360 }
361
362 if (acrossDays) {
363 DateTime start = new DateTime(startTime);
364
365
366
367
368
369
370
371
372
373
374
375
376 DateTime end = TKUtils.convertDateStringToDateTime(startDateS, endTimeS);
377
378 if (endTemp.getDayOfYear() - startTemp.getDayOfYear() < 1) {
379 end = new DateTime(endTime);
380 }
381 DateTime groupEnd = new DateTime(endTime);
382 Long startLong = start.getMillis();
383 Long endLong = end.getMillis();
384
385
386 DateMidnight midNight = new DateMidnight(endLong);
387 while (start.isBefore(groupEnd.getMillis()) && ((endLong >= startLong) || end.isEqual(midNight))) {
388 Interval tempInt = null;
389 if (end.isEqual(midNight)) {
390 tempInt = addedTimeblockInterval;
391 } else {
392 tempInt = new Interval(startLong, endLong);
393 }
394 dayInt.add(tempInt);
395 start = start.plusDays(1);
396 end = end.plusDays(1);
397 startLong = start.getMillis();
398 endLong = end.getMillis();
399 }
400 } else {
401 dayInt.add(addedTimeblockInterval);
402 }
403
404 for (TimeBlock timeBlock : timesheetDocument.getTimeBlocks()) {
405 if (errors.size() == 0 && StringUtils.equals(timeBlock.getEarnCodeType(), HrConstants.EARN_CODE_TIME)) {
406
407 Job aJob = HrServiceLocator.getJobService().getJob(timeBlock.getPrincipalId(), timeBlock.getJobNumber(), new LocalDate(timeBlock.getBeginDate()));
408 if(aJob != null && aJob.getPayTypeObj() != null && isRegularEarnCode && !StringUtils.equals(aJob.getPayTypeObj().getRegEarnCode(),timeBlock.getEarnCode())) {
409 continue;
410 }
411
412 Interval timeBlockInterval = new Interval(timeBlock.getBeginTimestamp().getTime(), timeBlock.getEndTimestamp().getTime());
413 for (Interval intv : dayInt) {
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428 DateTime start_dt_timezone = new DateTime(startTime);
429 DateTime end_dt_timezone = new DateTime(endTime);
430 Interval converted_intv = new Interval(start_dt_timezone.getMillis(), end_dt_timezone.getMillis());
431 List<Interval> intervals = new ArrayList<Interval>();
432 if (acrossDays) {
433 List<LocalDate> localDates = new ArrayList<LocalDate>();
434 LocalDate startDay = new LocalDate(start_dt_timezone);
435 DateTimeZone userTimeZone = DateTimeZone.forID(HrServiceLocator.getTimezoneService().getUserTimezone(timesheetDocument.getPrincipalId()));
436 if (userTimeZone==null) {
437 userTimeZone = HrServiceLocator.getTimezoneService().getTargetUserTimezoneWithFallback();
438 }
439
440
441 int days = end_dt_timezone.withZone(userTimeZone).toLocalTime().equals(new LocalTime(0,0,0)) ? Days.daysBetween(startDay, new LocalDate(end_dt_timezone)).getDays() : Days.daysBetween(startDay, new LocalDate(end_dt_timezone)).getDays()+1;
442 for (int i=0; i < days; i++) {
443 LocalDate d = startDay.withFieldAdded(DurationFieldType.days(), i);
444 localDates.add(d);
445 }
446 for (LocalDate localDate : localDates) {
447 DateTime startDateTime = localDate.toDateTime(start_dt_timezone.toLocalTime());
448 DateTime endDateTime = localDate.toDateTime(end_dt_timezone.toLocalTime());
449 endDateTime = endDateTime.isBefore(startDateTime) ? endDateTime.plusDays(1) : endDateTime;
450
451 intervals.add(new Interval(startDateTime,endDateTime));
452 }
453
454 } else {
455 intervals.add(converted_intv);
456 }
457
458 for (Interval interval : intervals) {
459 if (isRegularEarnCode && timeBlockInterval.overlaps(interval) && (timeblockId == null || timeblockId.compareTo(timeBlock.getTkTimeBlockId()) != 0)) {
460 errors.add("The time block you are trying to add overlaps with an existing time block.");
461 return errors;
462 }else if(timeBlock.getEarnCode().equals(selectedEarnCode) && timeBlockInterval.overlaps(interval) && (timeblockId == null || timeblockId.compareTo(timeBlock.getTkTimeBlockId()) != 0)){
463 errors.add("The time block you are trying to add overlaps with an existing time block.");
464 return errors;
465 }
466 }
467 }
468 }
469 }
470
471 return errors;
472 }
473
474
475
476
477
478
479
480 @Deprecated
481 public static List<String> validateDates(String startDateS, String endDateS) {
482 List<String> errors = new ArrayList<String>();
483 if (errors.size() == 0 && StringUtils.isEmpty(startDateS)) errors.add("The start date is blank.");
484 if (errors.size() == 0 && StringUtils.isEmpty(endDateS)) errors.add("The end date is blank.");
485 return errors;
486 }
487
488
489
490
491
492
493
494 @Deprecated
495 public static List<String> validateTimes(String startTimeS, String endTimeS) {
496 List<String> errors = new ArrayList<String>();
497 if (errors.size() == 0 && startTimeS == null) errors.add("The start time is blank.");
498 if (errors.size() == 0 && endTimeS == null) errors.add("The end time is blank.");
499 return errors;
500 }
501
502
503
504
505
506
507
508
509 @Deprecated
510 public static List<String> validateInterval(CalendarEntry payCalEntry, Long startTime, Long endTime) {
511 List<String> errors = new ArrayList<String>();
512 LocalDateTime pcb_ldt = payCalEntry.getBeginPeriodLocalDateTime();
513 LocalDateTime pce_ldt = payCalEntry.getEndPeriodLocalDateTime();
514
515 DateTime p_cal_b_dt = pcb_ldt.toDateTime();
516 DateTime p_cal_e_dt = pce_ldt.toDateTime();
517
518 Interval payInterval = new Interval(p_cal_b_dt, p_cal_e_dt);
519 if (errors.size() == 0 && !payInterval.contains(startTime)) {
520 errors.add("The start date/time is outside the pay period");
521 }
522 if (errors.size() == 0 && !payInterval.contains(endTime) && p_cal_e_dt.getMillis() != endTime) {
523 errors.add("The end date/time is outside the pay period");
524 }
525 return errors;
526 }
527
528 }