1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.hr.time.detail.validation;
17
18 import org.apache.commons.lang.StringUtils;
19 import org.joda.time.*;
20 import org.kuali.hr.time.assignment.Assignment;
21 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
22 import org.kuali.hr.time.calendar.CalendarEntries;
23 import org.kuali.hr.time.clocklog.ClockLog;
24 import org.kuali.hr.time.detail.web.TimeDetailActionFormBase;
25 import org.kuali.hr.time.earncode.EarnCode;
26 import org.kuali.hr.time.service.base.TkServiceLocator;
27 import org.kuali.hr.time.timeblock.TimeBlock;
28 import org.kuali.hr.time.timesheet.TimesheetDocument;
29 import org.kuali.hr.time.util.TKUser;
30 import org.kuali.hr.time.util.TKUtils;
31 import org.kuali.hr.time.util.TkConstants;
32
33 import java.math.BigDecimal;
34 import java.sql.Date;
35 import java.sql.Timestamp;
36 import java.util.ArrayList;
37 import java.util.List;
38
39 public class TimeDetailValidationUtil {
40
41
42
43
44
45
46
47 public static List<String> validateTimeEntryDetails(TimeDetailActionFormBase tdaf) {
48 return TimeDetailValidationUtil.validateTimeEntryDetails(
49 tdaf.getHours(), tdaf.getAmount(), tdaf.getStartTime(), tdaf.getEndTime(),
50 tdaf.getStartDate(), tdaf.getEndDate(), tdaf.getTimesheetDocument(),
51 tdaf.getSelectedEarnCode(), tdaf.getSelectedAssignment(),
52 tdaf.getAcrossDays().equalsIgnoreCase("y"), tdaf.getTkTimeBlockId(), tdaf.getOvertimePref(), tdaf.getSpanningWeeks().equalsIgnoreCase("y")
53 );
54 }
55
56 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, boolean spanningWeeks) {
57 List<String> errors = new ArrayList<String>();
58
59 if (timesheetDocument == null) {
60 errors.add("No timesheet document found.");
61 }
62 if (errors.size() > 0) return errors;
63
64 CalendarEntries payCalEntry = timesheetDocument.getCalendarEntry();
65 java.sql.Date asOfDate = payCalEntry.getEndPeriodDate();
66
67 errors.addAll(TimeDetailValidationUtil.validateDates(startDateS, endDateS));
68 errors.addAll(TimeDetailValidationUtil.validateTimes(startTimeS, endTimeS));
69 if (errors.size() > 0) return errors;
70
71 Long startTime;
72 Long endTime;
73
74 if (startTimeS.equals(endTimeS)
75 && startDateS.equals(endDateS)) {
76 startTime = TKUtils.convertDateStringToTimestampWithoutZone(startDateS, startTimeS).getTime();
77 endTime = TKUtils.convertDateStringToTimestampWithoutZone(endDateS, endTimeS).getTime();
78 } else {
79
80 startTime = TKUtils.convertDateStringToTimestamp(startDateS, startTimeS).getTime();
81 endTime = TKUtils.convertDateStringToTimestamp(endDateS, endTimeS).getTime();
82 }
83
84 errors.addAll(validateInterval(payCalEntry, startTime, endTime));
85 if (errors.size() > 0) return errors;
86
87 EarnCode earnCode = new EarnCode();
88 if (StringUtils.isNotBlank(selectedEarnCode)) {
89 earnCode = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, asOfDate);
90
91 if (earnCode != null && earnCode.getRecordMethod()!= null && earnCode.getRecordMethod().equalsIgnoreCase(TkConstants.EARN_CODE_TIME)) {
92 if (startTimeS == null) errors.add("The start time is blank.");
93 if (endTimeS == null) errors.add("The end time is blank.");
94 if (startTime - endTime == 0) errors.add("Start time and end time cannot be equivalent");
95 }
96 }
97 if (errors.size() > 0) return errors;
98
99 DateTime startTemp = new DateTime(startTime);
100 DateTime endTemp = new DateTime(endTime);
101
102 if (errors.size() == 0 && !acrossDays && !StringUtils.equals(TkConstants.EARN_CODE_CPE, overtimePref)) {
103 Hours hrs = Hours.hoursBetween(startTemp, endTemp);
104 if (hrs.getHours() >= 24) errors.add("One timeblock cannot exceed 24 hours");
105 }
106 if (errors.size() > 0) return errors;
107
108
109 AssignmentDescriptionKey assignKey = TkServiceLocator.getAssignmentService().getAssignmentDescriptionKey(selectedAssignment);
110 Assignment assign = TkServiceLocator.getAssignmentService().getAssignment(assignKey, new Date(startTime));
111 if (assign == null) errors.add("Assignment is not valid for start date " + TKUtils.formatDate(new Date(startTime)));
112 assign = TkServiceLocator.getAssignmentService().getAssignment(assignKey, new Date(endTime));
113 if (assign == null) errors.add("Assignment is not valid for end date " + TKUtils.formatDate(new Date(endTime)));
114 if (errors.size() > 0) return errors;
115
116
117
118
119
120
121
122
123
124
125
126 if ((startTime.compareTo(endTime) > 0 || endTime.compareTo(startTime) < 0)) {
127 errors.add("The time or date is not valid.");
128 }
129 if (errors.size() > 0) return errors;
130
131
132
133
134
135 errors.addAll(validateSpanningWeeks(spanningWeeks, startTemp, endTemp));
136 if (errors.size() > 0) return errors;
137
138
139
140
141 if (acrossDays && hours == null && amount == null) {
142 if (startTemp.getHourOfDay() >= endTemp.getHourOfDay()
143 && !(endTemp.getDayOfYear() - startTemp.getDayOfYear() <= 1
144 && endTemp.getHourOfDay() == 0)) {
145 errors.add("The \"apply to each day\" box should not be checked.");
146 }
147 }
148 if (errors.size() > 0) return errors;
149
150
151
152
153 if (amount != null && earnCode != null && StringUtils.equals(earnCode.getEarnCodeType(), TkConstants.EARN_CODE_AMOUNT)) {
154 if (amount.equals(BigDecimal.ZERO)) {
155 errors.add("Amount cannot be zero.");
156 }
157 if (amount.scale() > 2) {
158 errors.add("Amount cannot have more than two digits after decimal point.");
159 }
160 }
161 if (errors.size() > 0) return errors;
162
163
164
165
166
167 if (hours != null && earnCode != null && StringUtils.equals(earnCode.getEarnCodeType(), TkConstants.EARN_CODE_HOUR)) {
168 if (hours.equals(BigDecimal.ZERO)) {
169 errors.add("Hours cannot be zero.");
170 }
171 if (hours.scale() > 2) {
172 errors.add("Hours cannot have more than two digits after decimal point.");
173 }
174 int dayDiff = endTemp.getDayOfYear() - startTemp.getDayOfYear() + 1;
175 if (hours.compareTo(new BigDecimal(dayDiff * 24)) == 1) {
176 errors.add("Cannot enter more than 24 hours per day.");
177 }
178 }
179 if (errors.size() > 0) return errors;
180
181
182
183
184
185
186
187
188 boolean isRegularEarnCode = StringUtils.equals(assign.getJob().getPayTypeObj().getRegEarnCode(),selectedEarnCode);
189 errors.addAll(validateOverlap(startTime, endTime, acrossDays, startDateS, endTimeS,startTemp, endTemp, timesheetDocument, timeblockId, isRegularEarnCode));
190 if (errors.size() > 0) return errors;
191
192
193
194
195 return errors;
196 }
197
198 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) {
199 List<String> errors = new ArrayList<String>();
200 Interval addedTimeblockInterval = new Interval(startTime, endTime);
201 List<Interval> dayInt = new ArrayList<Interval>();
202
203
204 ClockLog lastClockLog = TkServiceLocator.getClockLogService().getLastClockLog(TKUser.getCurrentTargetPersonId());
205 if(lastClockLog != null &&
206 (lastClockLog.getClockAction().equals(TkConstants.CLOCK_IN)
207 || lastClockLog.getClockAction().equals(TkConstants.LUNCH_IN))) {
208 Timestamp lastClockTimestamp = lastClockLog.getClockTimestamp();
209 String lastClockZone = lastClockLog.getClockTimestampTimezone();
210 if (StringUtils.isEmpty(lastClockZone)) {
211 lastClockZone = TKUtils.getSystemTimeZone();
212 }
213 DateTimeZone zone = DateTimeZone.forID(lastClockZone);
214 DateTime clockWithZone = new DateTime(lastClockTimestamp, zone);
215 DateTime currentTime = new DateTime(System.currentTimeMillis(), zone);
216 Interval currentClockInInterval = new Interval(clockWithZone.getMillis(), currentTime.getMillis());
217
218 if (isRegularEarnCode && addedTimeblockInterval.overlaps(currentClockInInterval)) {
219 errors.add("The time block you are trying to add overlaps with the current clock action.");
220 return errors;
221 }
222 }
223
224 if (acrossDays) {
225 DateTime start = new DateTime(startTime);
226 DateTime end = new DateTime(TKUtils.convertDateStringToTimestamp(startDateS, endTimeS).getTime());
227 if (endTemp.getDayOfYear() - startTemp.getDayOfYear() < 1) {
228 end = new DateTime(endTime);
229 }
230 DateTime groupEnd = new DateTime(endTime);
231 Long startLong = start.getMillis();
232 Long endLong = end.getMillis();
233
234
235 DateMidnight midNight = new DateMidnight(endLong);
236 while (start.isBefore(groupEnd.getMillis()) && ((endLong >= startLong) || end.isEqual(midNight))) {
237 Interval tempInt = null;
238 if (end.isEqual(midNight)) {
239 tempInt = addedTimeblockInterval;
240 } else {
241 tempInt = new Interval(startLong, endLong);
242 }
243 dayInt.add(tempInt);
244 start = start.plusDays(1);
245 end = end.plusDays(1);
246 startLong = start.getMillis();
247 endLong = end.getMillis();
248 }
249 } else {
250 dayInt.add(addedTimeblockInterval);
251 }
252
253 for (TimeBlock timeBlock : timesheetDocument.getTimeBlocks()) {
254 if (errors.size() == 0 && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) {
255 Interval timeBlockInterval = new Interval(timeBlock.getBeginTimestamp().getTime(), timeBlock.getEndTimestamp().getTime());
256 for (Interval intv : dayInt) {
257 if (isRegularEarnCode && timeBlockInterval.overlaps(intv) && (timeblockId == null || timeblockId.compareTo(timeBlock.getTkTimeBlockId()) != 0)) {
258 errors.add("The time block you are trying to add overlaps with an existing time block.");
259 }
260 }
261 }
262 }
263
264 return errors;
265 }
266
267 public static List<String> validateDates(String startDateS, String endDateS) {
268 List<String> errors = new ArrayList<String>();
269 if (errors.size() == 0 && StringUtils.isEmpty(startDateS)) errors.add("The start date is blank.");
270 if (errors.size() == 0 && StringUtils.isEmpty(endDateS)) errors.add("The end date is blank.");
271 return errors;
272 }
273
274 public static List<String> validateTimes(String startTimeS, String endTimeS) {
275 List<String> errors = new ArrayList<String>();
276 if (errors.size() == 0 && startTimeS == null) errors.add("The start time is blank.");
277 if (errors.size() == 0 && endTimeS == null) errors.add("The end time is blank.");
278 return errors;
279 }
280
281 public static List<String> validateInterval(CalendarEntries payCalEntry, Long startTime, Long endTime) {
282 List<String> errors = new ArrayList<String>();
283 LocalDateTime pcb_ldt = payCalEntry.getBeginLocalDateTime();
284 LocalDateTime pce_ldt = payCalEntry.getEndLocalDateTime();
285 DateTimeZone utz = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
286 DateTime p_cal_b_dt = pcb_ldt.toDateTime(utz);
287 DateTime p_cal_e_dt = pce_ldt.toDateTime(utz);
288
289 Interval payInterval = new Interval(p_cal_b_dt, p_cal_e_dt);
290 if (errors.size() == 0 && !payInterval.contains(startTime)) {
291 errors.add("The start date/time is outside the pay period");
292 }
293 if (errors.size() == 0 && !payInterval.contains(endTime) && p_cal_e_dt.getMillis() != endTime) {
294 errors.add("The end date/time is outside the pay period");
295 }
296 return errors;
297 }
298
299
300 public static List<String> validateSpanningWeeks(boolean spanningWeeks, DateTime startTemp, DateTime endTemp) {
301 List<String> errors = new ArrayList<String>();
302 boolean valid = true;
303 while ((startTemp.isBefore(endTemp) || startTemp.isEqual(endTemp)) && valid) {
304 if (!spanningWeeks &&
305 (startTemp.getDayOfWeek() == DateTimeConstants.SATURDAY || startTemp.getDayOfWeek() == DateTimeConstants.SUNDAY)) {
306 valid = false;
307 }
308 startTemp = startTemp.plusDays(1);
309 }
310 if (!valid) {
311 errors.add("Weekend day is selected, but include weekends checkbox is not checked");
312 }
313 return errors;
314 }
315 }