1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.hr.lm.leavecalendar.validation;
17
18 import org.apache.commons.collections.CollectionUtils;
19 import org.apache.commons.lang.StringUtils;
20 import org.joda.time.DateMidnight;
21 import org.joda.time.DateTime;
22 import org.joda.time.DateTimeConstants;
23 import org.joda.time.DateTimeZone;
24 import org.joda.time.Hours;
25 import org.joda.time.Interval;
26 import org.joda.time.LocalDateTime;
27 import org.kuali.hr.lm.LMConstants;
28 import org.kuali.hr.lm.accrual.AccrualCategory;
29 import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
30 import org.kuali.hr.lm.leave.web.LeaveCalendarWSForm;
31 import org.kuali.hr.lm.leaveSummary.LeaveSummary;
32 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
33 import org.kuali.hr.lm.leaveblock.LeaveBlock;
34 import org.kuali.hr.lm.leavecalendar.LeaveCalendarDocument;
35 import org.kuali.hr.time.assignment.Assignment;
36 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
37 import org.kuali.hr.time.base.web.TkCommonCalendarForm;
38 import org.kuali.hr.time.calendar.CalendarEntries;
39 import org.kuali.hr.time.earncode.EarnCode;
40 import org.kuali.hr.time.earncodegroup.EarnCodeGroup;
41 import org.kuali.hr.time.service.base.TkServiceLocator;
42 import org.kuali.hr.time.util.TKContext;
43 import org.kuali.hr.time.util.TKUser;
44 import org.kuali.hr.time.util.TKUtils;
45 import org.kuali.hr.time.util.TkConstants;
46
47 import java.math.BigDecimal;
48 import java.sql.Date;
49 import java.sql.Timestamp;
50 import java.util.*;
51
52 import org.kuali.rice.kew.api.KewApiServiceLocator;
53 import org.kuali.rice.kew.api.document.DocumentStatus;
54 import org.kuali.rice.krad.util.ObjectUtils;
55
56 public class LeaveCalendarValidationUtil {
57
58
59 public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveCalendarWSForm lcf) {
60 LeaveBlock updatedLeaveBlock = null;
61 if(lcf.getLeaveBlockId() != null) {
62 updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId());
63 }
64 return validateLeaveAccrualRuleMaxUsage(lcf.getLeaveSummary(), lcf.getSelectedEarnCode(), lcf.getStartDate(),
65 lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock);
66 }
67
68 public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveSummary ls, String selectedEarnCode, String leaveStartDateString,
69 String leaveEndDateString, BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) {
70 List<String> errors = new ArrayList<String>();
71 String principalId = TKContext.getTargetPrincipalId();
72 long daysSpan = TKUtils.getDaysBetween(TKUtils.formatDateString(leaveStartDateString), TKUtils.formatDateString(leaveEndDateString));
73 if(leaveAmount == null) {
74 leaveAmount = TKUtils.getHoursBetween(TKUtils.formatDateString(leaveStartDateString).getTime(), TKUtils.formatDateString(leaveEndDateString).getTime());
75 }
76 if(ls != null && CollectionUtils.isNotEmpty(ls.getLeaveSummaryRows())) {
77 BigDecimal oldLeaveAmount = null;
78 boolean earnCodeChanged = false;
79 if(updatedLeaveBlock != null) {
80 if(!updatedLeaveBlock.getEarnCode().equals(selectedEarnCode)) {
81 earnCodeChanged = true;
82 }
83 if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) {
84 oldLeaveAmount = updatedLeaveBlock.getLeaveAmount();
85 }
86 }
87 Date aDate = TKUtils.formatDateString(leaveEndDateString);
88 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, aDate);
89 if(earnCodeObj != null) {
90 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), aDate);
91 if(accrualCategory != null) {
92 List<LeaveSummaryRow> rows = ls.getLeaveSummaryRows();
93 for(LeaveSummaryRow aRow : rows) {
94 if(aRow.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
95
96 List<EmployeeOverride> employeeOverrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId,TKUtils.formatDateString(leaveEndDateString));
97 String leavePlan = accrualCategory.getLeavePlan();
98 BigDecimal maxUsage = aRow.getUsageLimit();
99 for(EmployeeOverride eo : employeeOverrides) {
100 if(eo.getLeavePlan().equals(leavePlan) && eo.getAccrualCategory().equals(aRow.getAccrualCategory())) {
101 if(eo.getOverrideType().equals("MU") && eo.isActive()) {
102 if(eo.getOverrideValue()!=null) {
103 maxUsage = new BigDecimal(eo.getOverrideValue());
104 } else {
105 maxUsage = null;
106 }
107 }
108 }
109 }
110 BigDecimal ytdUsage = aRow.getYtdApprovedUsage();
111 BigDecimal pendingLeaveBalance = aRow.getPendingLeaveRequests();
112 BigDecimal desiredUsage = new BigDecimal(0);
113 if(pendingLeaveBalance!=null) {
114 if(oldLeaveAmount!=null) {
115
116 if(!earnCodeChanged ||
117 updatedLeaveBlock.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
118 pendingLeaveBalance = pendingLeaveBalance.subtract(oldLeaveAmount.abs());
119 }
120 }
121
122 desiredUsage = desiredUsage.add(pendingLeaveBalance);
123 }
124
125 desiredUsage = desiredUsage.add(leaveAmount.multiply(new BigDecimal(daysSpan+1)));
126
127 if(ytdUsage!=null) {
128 desiredUsage = desiredUsage.add(ytdUsage);
129 }
130 if(maxUsage!=null) {
131 if(desiredUsage.compareTo(maxUsage) > 0 ) {
132 errors.add("This leave request would exceed the usage limit for " + aRow.getAccrualCategory());
133 }
134 }
135 }
136 }
137 }
138 }
139 }
140 return errors;
141 }
142
143
144
145 public static Map<String, Set<String>> validatePendingTransactions(String principalId, Date fromDate, Date toDate) {
146 Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>();
147
148 Set<String> actionMessages = new HashSet<String>();
149 Set<String> infoMessages = new HashSet<String>();
150 Set<String> warningMessages = new HashSet<String>();
151
152 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(principalId, fromDate, toDate, LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
153 Set<String> workflowDocIds = new HashSet<String>();
154 for(LeaveBlock lb : leaveBlocks) {
155 if(lb.getTransactionalDocId() != null) {
156 workflowDocIds.add(lb.getTransactionalDocId());
157 } else {
158 if(StringUtils.contains(lb.getDescription(), "Forfeited balance transfer amount")) {
159 infoMessages.add("A max balance action that forfeited accrued leave occurred on this calendar");
160 }
161 }
162 }
163 for(String workflowDocId : workflowDocIds) {
164 DocumentStatus status = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(workflowDocId);
165
166 if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.FINAL)) {
167 infoMessages.add("A transfer action occurred on this calendar");
168 }
169 else if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.ENROUTE)) {
170 actionMessages.add("A pending balance transfer exists on this calendar. It must be finalized before this calendar can be approved");
171 }
172 else {
173 warningMessages.add("A balance transfer document exists for this calendar with status neither final nor enroute");
174 }
175 }
176
177 leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(principalId, fromDate, toDate, LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT);
178 workflowDocIds = new HashSet<String>();
179 for(LeaveBlock lb : leaveBlocks) {
180 if(lb.getTransactionalDocId() != null) {
181 workflowDocIds.add(lb.getTransactionalDocId());
182 }
183 }
184 for(String workflowDocId : workflowDocIds) {
185 DocumentStatus status = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(workflowDocId);
186
187 if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.FINAL)) {
188 infoMessages.add("A payout action occurred on this calendar");
189 }
190 else if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.ENROUTE)) {
191 actionMessages.add("A pending payout exists on this calendar. It must be finalized before this calendar can be approved");
192 }
193 else {
194 warningMessages.add("A payout document exists for this calendar with status neither final or enroute");
195 }
196 }
197 allMessages.put("actionMessages", actionMessages);
198 allMessages.put("infoMessages", infoMessages);
199 allMessages.put("warningMessages", warningMessages);
200
201 return allMessages;
202 }
203
204
205 public static Map<String, Set<String>> getWarningMessagesForLeaveBlocks(List<LeaveBlock> leaveBlocks) {
206
207 Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>();
208
209 Set<String> actionMessages = new HashSet<String>();
210 Set<String> infoMessages = new HashSet<String>();
211 Set<String> warningMessages = new HashSet<String>();
212
213 if (CollectionUtils.isNotEmpty(leaveBlocks)) {
214 for(LeaveBlock lb : leaveBlocks) {
215 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), lb.getLeaveDate());
216 if(ec != null) {
217 EarnCodeGroup eg = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupForEarnCode(lb.getEarnCode(), lb.getLeaveDate());
218 if(eg != null && !StringUtils.isEmpty(eg.getWarningText())) {
219 warningMessages.add(eg.getWarningText());
220 }
221 }
222 }
223 }
224 allMessages.put("actionMessages", actionMessages);
225 allMessages.put("infoMessages", infoMessages);
226 allMessages.put("warningMessages", warningMessages);
227
228
229 return allMessages;
230 }
231
232 public static List<String> validateAvailableLeaveBalance(LeaveCalendarWSForm lcf) {
233 LeaveBlock updatedLeaveBlock = null;
234 if(lcf.getLeaveBlockId() != null) {
235 updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId());
236 }
237 return validateAvailableLeaveBalanceForUsage(lcf.getSelectedEarnCode(), lcf.getStartDate(), lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock);
238 }
239
240 public static List<String> validateAvailableLeaveBalanceForUsage(String earnCode, String leaveStartDateString, String leaveEndDateString,
241 BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) {
242 List<String> errors = new ArrayList<String>();
243 boolean earnCodeChanged = false;
244 BigDecimal oldAmount = null;
245
246 if(leaveAmount == null) {
247 leaveAmount = TKUtils.getHoursBetween(TKUtils.formatDateString(leaveStartDateString).getTime(), TKUtils.formatDateString(leaveEndDateString).getTime());
248 }
249 if(updatedLeaveBlock != null) {
250 if(!updatedLeaveBlock.getEarnCode().equals(earnCode)) {
251 earnCodeChanged = true;
252 }
253 if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) {
254 oldAmount = updatedLeaveBlock.getLeaveAmount();
255 }
256 }
257 Date startDate = TKUtils.formatDateString(leaveStartDateString);
258 Date endDate = TKUtils.formatDateString(leaveEndDateString);
259 long daysSpan = TKUtils.getDaysBetween(startDate,endDate);
260 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, endDate);
261 if(earnCodeObj != null && earnCodeObj.getAllowNegativeAccrualBalance().equals("N")) {
262 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), endDate);
263 if(accrualCategory != null) {
264 java.util.Date nextIntervalDate = TkServiceLocator.getAccrualService().getNextAccrualIntervalDate(accrualCategory.getAccrualEarnInterval(), endDate);
265
266 java.util.Date usageEndDate = nextIntervalDate;
267 if(nextIntervalDate.compareTo(endDate) > 0) {
268 Calendar aCal = Calendar.getInstance();
269 aCal.setTime(nextIntervalDate);
270 aCal.add(Calendar.DAY_OF_YEAR, -1);
271 usageEndDate = aCal.getTime();
272 }
273
274
275 if(accrualCategory.getAccrualEarnInterval().equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) {
276 Calendar aCal = Calendar.getInstance();
277 aCal.setTime(endDate);
278 aCal.set(Calendar.MONTH, Calendar.DECEMBER);
279 aCal.set(Calendar.DAY_OF_MONTH, 31);
280 nextIntervalDate = aCal.getTime();
281 usageEndDate = nextIntervalDate;
282 }
283 BigDecimal availableBalance = TkServiceLocator.getLeaveSummaryService()
284 .getLeaveBalanceForAccrCatUpToDate(TKContext.getTargetPrincipalId(), startDate, endDate, accrualCategory.getAccrualCategory(), usageEndDate);
285
286 if(oldAmount!=null) {
287 if(!earnCodeChanged ||
288 updatedLeaveBlock.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) {
289 availableBalance = availableBalance.add(oldAmount.abs());
290 }
291 }
292
293 BigDecimal desiredUsage =null;
294 if(!TkConstants.EARN_CODE_TIME.equals(earnCodeObj.getRecordMethod())) {
295 desiredUsage = leaveAmount.multiply(new BigDecimal(daysSpan+1));
296 } else {
297 desiredUsage = leaveAmount.multiply(new BigDecimal(daysSpan));
298 }
299
300 if(desiredUsage.compareTo(availableBalance) > 0 ) {
301 errors.add("Requested leave amount " + desiredUsage.toString() + " is greater than available leave balance " + availableBalance.toString());
302 }
303 }
304 }
305
306 return errors;
307 }
308
309 public static List<String> validateDates(String startDateS, String endDateS) {
310 List<String> errors = new ArrayList<String>();
311 if (errors.size() == 0 && StringUtils.isEmpty(startDateS)) errors.add("The start date is blank.");
312 if (errors.size() == 0 && StringUtils.isEmpty(endDateS)) errors.add("The end date is blank.");
313 return errors;
314 }
315
316 public static List<String> validateTimes(String startTimeS, String endTimeS) {
317 List<String> errors = new ArrayList<String>();
318 if (errors.size() == 0 && startTimeS == null) errors.add("The start time is blank.");
319 if (errors.size() == 0 && endTimeS == null) errors.add("The end time is blank.");
320 return errors;
321 }
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 public static List<String> validateSpanningWeeks(LeaveCalendarWSForm lcf) {
381 boolean spanningWeeks = lcf.getSpanningWeeks().equalsIgnoreCase("y");
382 Date startDate = TKUtils.formatDateString(lcf.getStartDate());
383 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(lcf.getSelectedEarnCode(), startDate);
384 DateTime startTemp, endTemp;
385
386 if (ec != null && !ec.getRecordMethod().equals(LMConstants.RECORD_METHOD.TIME)) {
387 startTemp = new DateTime(startDate);
388 endTemp = new DateTime(TKUtils.formatDateString(lcf.getEndDate()));
389 } else {
390 startTemp = new DateTime(TKUtils.convertDateStringToTimestamp(lcf.getStartDate()).getTime());
391 endTemp = new DateTime(TKUtils.convertDateStringToTimestamp(lcf.getEndDate()).getTime());
392 }
393
394 List<String> errors = new ArrayList<String>();
395 boolean valid = true;
396 while ((startTemp.isBefore(endTemp) || startTemp.isEqual(endTemp)) && valid) {
397 if (!spanningWeeks &&
398 (startTemp.getDayOfWeek() == DateTimeConstants.SATURDAY || startTemp.getDayOfWeek() == DateTimeConstants.SUNDAY)) {
399 valid = false;
400 }
401 startTemp = startTemp.plusDays(1);
402 }
403 if (!valid) {
404 errors.add("Weekend day is selected, but include weekends checkbox is not checked");
405 }
406 return errors;
407 }
408
409 public static List<String> validateParametersAccordingToSelectedEarnCodeRecordMethod(LeaveCalendarWSForm lcf) {
410 return validateParametersForLeaveEntry(lcf.getSelectedEarnCode(), lcf.getCalendarEntry(), lcf.getStartDate(), lcf.getEndDate(), lcf.getStartTime(), lcf.getEndTime(), lcf.getSelectedAssignment(), lcf.getLeaveCalendarDocument(), lcf.getLeaveBlockId());
411 }
412
413 public static List<String> validateParametersForLeaveEntry(String selectedEarnCode, CalendarEntries leaveCalEntry, String startDateS, String endDateS, String startTimeS, String endTimeS, String selectedAssignment, LeaveCalendarDocument leaveCalendarDocument, String leaveBlockId) {
414
415 java.sql.Date asOfDate = leaveCalEntry.getEndPeriodDate();
416
417 List<String> errors = new ArrayList<String>();
418 if (StringUtils.isNotBlank(selectedEarnCode)) {
419 EarnCode earnCode = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, asOfDate);
420
421 if(earnCode != null && earnCode.getRecordMethod().equalsIgnoreCase(TkConstants.EARN_CODE_TIME)) {
422
423 errors.addAll(LeaveCalendarValidationUtil.validateDates(startDateS, endDateS));
424 errors.addAll(LeaveCalendarValidationUtil.validateTimes(startTimeS, endTimeS));
425 if (errors.size() > 0) return errors;
426
427 Long startTime;
428 Long endTime;
429
430 startTime = TKUtils.convertDateStringToTimestampWithoutZone(startDateS, startTimeS).getTime();
431 endTime = TKUtils.convertDateStringToTimestampWithoutZone(endDateS, endTimeS).getTime();
432
433 errors.addAll(validateInterval(leaveCalEntry, startTime, endTime));
434 if (errors.size() > 0) return errors;
435
436 if (startTimeS == null) errors.add("The start time is blank.");
437 if (endTimeS == null) errors.add("The end time is blank.");
438 if (startTime - endTime == 0) errors.add("Start time and end time cannot be equivalent");
439
440 if (errors.size() > 0) return errors;
441
442 DateTime startTemp = new DateTime(startTime);
443 DateTime endTemp = new DateTime(endTime);
444
445 if (errors.size() == 0) {
446 Hours hrs = Hours.hoursBetween(startTemp, endTemp);
447 if (hrs.getHours() >= 24) errors.add("One leaveblock cannot exceed 24 hours");
448 }
449 if (errors.size() > 0) return errors;
450
451
452 AssignmentDescriptionKey assignKey = TkServiceLocator.getAssignmentService().getAssignmentDescriptionKey(selectedAssignment);
453 Assignment assign = TkServiceLocator.getAssignmentService().getAssignment(assignKey, new Date(startTime));
454
455 if ((startTime.compareTo(endTime) > 0 || endTime.compareTo(startTime) < 0)) {
456 errors.add("The time or date is not valid.");
457 }
458 if (errors.size() > 0) return errors;
459
460
461 boolean isRegularEarnCode = true;
462 errors.addAll(validateOverlap(startTime, endTime, startDateS, endTimeS,startTemp, endTemp, leaveCalEntry, leaveBlockId, isRegularEarnCode, earnCode.getRecordMethod()));
463 if (errors.size() > 0) return errors;
464 }
465 }
466 return errors;
467 }
468
469 public static List<String> validateInterval(CalendarEntries payCalEntry, Long startTime, Long endTime) {
470 List<String> errors = new ArrayList<String>();
471 LocalDateTime pcb_ldt = payCalEntry.getBeginLocalDateTime();
472 LocalDateTime pce_ldt = payCalEntry.getEndLocalDateTime();
473 DateTimeZone utz = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
474 DateTime p_cal_b_dt = pcb_ldt.toDateTime(utz);
475 DateTime p_cal_e_dt = pce_ldt.toDateTime(utz);
476
477 Interval payInterval = new Interval(p_cal_b_dt, p_cal_e_dt);
478 if (errors.size() == 0 && !payInterval.contains(startTime)) {
479 errors.add("The start date/time is outside the pay period");
480 }
481 if (errors.size() == 0 && !payInterval.contains(endTime) && p_cal_e_dt.getMillis() != endTime) {
482 errors.add("The end date/time is outside the pay period");
483 }
484 return errors;
485 }
486
487 public static List<String> validateOverlap(Long startTime, Long endTime, String startDateS, String endTimeS, DateTime startTemp, DateTime endTemp, CalendarEntries calendarEntry, String lmLeaveBlockId, boolean isRegularEarnCode, String earnCodeType) {
488 List<String> errors = new ArrayList<String>();
489 Interval addedTimeblockInterval = new Interval(startTime, endTime);
490 List<Interval> dayInt = new ArrayList<Interval>();
491 String viewPrincipal = TKUser.getCurrentTargetPersonId();
492
493 dayInt.add(addedTimeblockInterval);
494 List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(viewPrincipal, calendarEntry);
495 List<String> assignmentKeys = new ArrayList<String>();
496 for(Assignment assign : assignments) {
497 assignmentKeys.add(assign.getAssignmentKey());
498 }
499
500 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForLeaveCalendar(viewPrincipal, calendarEntry.getBeginPeriodDate(), calendarEntry.getEndPeriodDate(), assignmentKeys);
501 for (LeaveBlock leaveBlock : leaveBlocks) {
502 if (errors.size() == 0 && StringUtils.equals(earnCodeType, TkConstants.EARN_CODE_TIME) && leaveBlock.getBeginTimestamp() != null && leaveBlock.getEndTimestamp()!= null) {
503 Interval leaveBlockInterval = new Interval(leaveBlock.getBeginTimestamp().getTime(), leaveBlock.getEndTimestamp().getTime());
504 for (Interval intv : dayInt) {
505 if (isRegularEarnCode && leaveBlockInterval.overlaps(intv) && (lmLeaveBlockId == null || lmLeaveBlockId.compareTo(leaveBlock.getLmLeaveBlockId()) != 0)) {
506 errors.add("The leave block you are trying to add overlaps with an existing time block.");
507 }
508 }
509 }
510 }
511
512 return errors;
513 }
514 }