001 /**
002 * Copyright 2004-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.hr.time.missedpunch;
017
018 import java.sql.Timestamp;
019 import java.text.ParseException;
020 import java.text.SimpleDateFormat;
021 import java.util.Date;
022 import java.util.HashSet;
023 import java.util.List;
024 import java.util.Set;
025
026 import org.apache.commons.lang.StringUtils;
027 import org.joda.time.DateTime;
028 import org.joda.time.DateTimeZone;
029 import org.joda.time.LocalTime;
030 import org.kuali.hr.time.assignment.Assignment;
031 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
032 import org.kuali.hr.time.clocklog.ClockLog;
033 import org.kuali.hr.time.service.base.TkServiceLocator;
034 import org.kuali.hr.time.timesheet.TimesheetDocument;
035 import org.kuali.hr.time.util.TKContext;
036 import org.kuali.hr.time.util.TKUtils;
037 import org.kuali.hr.time.util.TkConstants;
038 import org.kuali.rice.kew.api.KewApiServiceLocator;
039 import org.kuali.rice.kew.api.document.DocumentStatus;
040 import org.kuali.rice.krad.document.Document;
041 import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
042 import org.kuali.rice.krad.util.GlobalVariables;
043
044 public class MissedPunchValidation extends TransactionalDocumentRuleBase {
045
046 @Override
047 protected boolean processCustomRouteDocumentBusinessRules(Document document) {
048 boolean valid = true;
049
050 MissedPunchDocument missedPunchDocument = (MissedPunchDocument) document;
051 DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(missedPunchDocument.getDocumentNumber());
052
053 if (DocumentStatus.INITIATED.equals(DocumentStatus.fromCode(documentStatus.getCode()))
054 || DocumentStatus.SAVED.equals(DocumentStatus.fromCode(documentStatus.getCode()))) {
055 valid &= validateTimeSheet(missedPunchDocument);
056 valid &= validateAssignment(missedPunchDocument);
057
058 if (valid) {
059 ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(missedPunchDocument.getPrincipalId());
060 try {
061 valid &= validateClockAction(missedPunchDocument, lastClock);
062 valid &= validateClockTime(missedPunchDocument, lastClock);
063 } catch (ParseException e) {
064 e.printStackTrace();
065 }
066 }
067 }
068
069 return valid;
070 }
071
072 // do not allow a missed punch if the time sheet document is enroute or final
073 boolean validateTimeSheet(MissedPunchDocument mp) {
074 boolean valid = true;
075 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
076 if(tsd != null
077 && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)
078 || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
079 GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
080 valid = false;
081 }
082
083 return valid;
084 }
085
086 /**
087 * Checks the provided MissedPunch for a valid ClockAction.
088 *
089 * @param mp The MissedPunch we are validating.
090 * @param lastClock The ClockLog entry that happened before the one we want to create.
091 *
092 * @return true if valid, false otherwise.
093 */
094 boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
095 boolean valid = true;
096 Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
097
098
099 if (mp.getClockAction().equals(TkConstants.CLOCK_OUT) || mp.getClockAction().equals(TkConstants.LUNCH_OUT)) {
100 ClockLog lci = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.CLOCK_IN); //last clock in
101 ClockLog lli = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.LUNCH_IN); //last lunch in
102 if (lci != null) {
103 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lci.getTkClockLogId());
104 if(mpd != null) {
105 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
106 return false;
107 }
108 } else if(lli != null) {
109 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lli.getTkClockLogId());
110 if(mpd != null) {
111 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
112 return false;
113 }
114 }
115 }
116
117 // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut
118 // missed punch can only be used on a tiemblock once.
119 // if(lastClock != null
120 // && (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) {
121 // MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId());
122 // if(mpd != null) {
123 // GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
124 // return false;
125 // }
126 // }
127 if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) {
128 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action");
129 valid = false;
130 }
131
132 return valid;
133 }
134
135 /**
136 * Checks whether the chosen assignment is valid (both chosen and within proper timeframe).
137 * Checks whether the provided MissedPunch has a valid time based on the
138 * previous ClockLog.
139 *
140 * @param mp The MissedPunch we are validating.
141 * @param lastClock The ClockLog entry that happened before the one we want to create.
142 *
143 * @return true if valid, false otherwise.
144 * @throws ParseException
145 */
146 boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException {
147 boolean valid = true;
148
149 if (lastClock == null) {
150 return valid;
151 }
152
153 //Missed Action Date and Missed Action Time are required fields. KPME-1853
154 if(mp.getActionTime() == null || mp.getActionDate() == null)
155 return false;
156
157 DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime());
158 DateTime boundaryMax = clockLogDateTime.plusDays(1);
159 DateTime nowTime = new DateTime(TKUtils.getCurrentDate());
160 long offset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback());
161 long dateTimeLocal = new LocalTime(mp.getActionTime()).getMillisOfDay() + mp.getActionDate().getTime() - offset;
162
163 //this will be in system's timezone, but offset with user's timezone
164 DateTime actionDateTime = new DateTime(dateTimeLocal);
165
166 // if date is a future date
167 if(actionDateTime.getYear()> nowTime.getYear()
168 || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) {
169 GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date");
170 return false;
171 }
172
173 // if time is a future time
174 if(actionDateTime.getMillis() > nowTime.getMillis()) {
175 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time");
176 return false;
177 }
178
179 if ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax))
180 || actionDateTime.isBefore(clockLogDateTime)) {
181 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime");
182 valid = false;
183 }
184
185 return valid;
186 }
187
188 /**
189 * Checks whether the chosen assignment is valid (both chosen and within proper timeframe).
190 *
191 * @param mp The MissedPunch we are validating.
192 *
193 * @return true if valid, false otherwise.
194 * @throws ParseException
195 */
196 boolean validateAssignment(MissedPunchDocument mp) {
197 boolean valid = true;
198
199 String assignment = mp.getAssignment();
200
201 if (mp.getAssignment() == null) {
202 GlobalVariables.getMessageMap().putError("document.assignment", "clock.mp.assignment.required");
203 valid = false;
204 }
205
206 if (valid)
207 {
208 AssignmentDescriptionKey key = new AssignmentDescriptionKey(assignment);
209 if (key != null) {
210 Assignment assignmentObj = TkServiceLocator.getAssignmentService().getAssignment(mp.getPrincipalId(), key, new java.sql.Date(mp.getActionDate().getTime()));
211 if (assignmentObj == null) {
212 GlobalVariables.getMessageMap().putError("document.assignment", "clock.mp.assignment.active");
213 valid = false;
214 }
215 }
216 else {
217 valid = false;
218 }
219 }
220
221 return valid;
222 }
223 }