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.Set;
024
025 import org.apache.commons.lang.StringUtils;
026 import org.joda.time.DateTime;
027 import org.joda.time.DateTimeZone;
028 import org.joda.time.LocalTime;
029 import org.kuali.hr.time.clocklog.ClockLog;
030 import org.kuali.hr.time.service.base.TkServiceLocator;
031 import org.kuali.hr.time.timesheet.TimesheetDocument;
032 import org.kuali.hr.time.util.TKUtils;
033 import org.kuali.hr.time.util.TkConstants;
034 import org.kuali.rice.krad.document.Document;
035 import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
036 import org.kuali.rice.krad.util.GlobalVariables;
037
038 public class MissedPunchValidation extends TransactionalDocumentRuleBase {
039
040 /**
041 * Checks the provided MissedPunch for a valid ClockAction.
042 *
043 * @param mp The MissedPunch we are validating.
044 * @param lastClock The ClockLog entry that happened before the one we want to create.
045 *
046 * @return true if valid, false otherwise.
047 */
048 boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
049 boolean valid = true;
050 Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
051
052 // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut
053 // missed punch can only be used on a tiemblock once.
054 if(lastClock != null
055 && (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) {
056 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId());
057 if(mpd != null) {
058 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
059 return false;
060 }
061 }
062 if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) {
063 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action");
064 valid = false;
065 }
066
067 return valid;
068 }
069
070 /**
071 * Checks whether the provided MissedPunch has a valid time based on the
072 * previous ClockLog.
073 *
074 * @param mp The MissedPunch we are validating.
075 * @param lastClock The ClockLog entry that happened before the one we want to create.
076 *
077 * @return true if valid, false otherwise.
078 * @throws ParseException
079 */
080 boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException {
081 boolean valid = true;
082
083 if (lastClock == null)
084 return valid;
085
086 //Missed Action Date and Missed Action Time are required fields. KPME-1853
087 if(mp.getActionTime() == null || mp.getActionDate() == null)
088 return false;
089
090 DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime());
091 DateTime boundaryMax = clockLogDateTime.plusDays(1);
092 DateTime nowTime = new DateTime(TKUtils.getCurrentDate());
093
094 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
095 String s = formatter.format(mp.getActionDate());
096 Date tempDate = formatter.parse(s);
097 Timestamp dateLocal = new Timestamp(tempDate.getTime());
098 LocalTime timeLocal = new LocalTime(mp.getActionTime().getTime());
099 DateTime actionDateTime = new DateTime(dateLocal.getTime());
100 actionDateTime = actionDateTime.plus(timeLocal.getMillisOfDay());
101
102 // convert the action time to the system zone
103 Timestamp ts = new Timestamp(actionDateTime.getMillis());
104 ClockLog lastLog = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId());
105 Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastLog.getClockTimestampTimezone()));
106 Timestamp actionTime = new Timestamp(ts.getTime()-zoneOffset);
107 DateTime newDateTime = new DateTime(actionTime.getTime());
108
109 // if date is a future date
110 if(actionDateTime.getYear()> nowTime.getYear()
111 || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) {
112 GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date");
113 return false;
114 }
115
116 // if time is a future time
117 if(actionDateTime.getMillis() > nowTime.getMillis()) {
118 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time");
119 return false;
120 }
121
122 if ( ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax))
123 || newDateTime.isBefore(clockLogDateTime)) && StringUtils.equals(mp.getDocumentStatus(),"R")) {
124 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime");
125 valid = false;
126 }
127
128 return valid;
129 }
130
131 // do not allow a missed punch if the time sheet document is enroute or final
132 boolean validateTimeSheet(MissedPunchDocument mp) {
133 boolean valid = true;
134 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
135 if(tsd != null
136 && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)
137 || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
138 GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
139 valid = false;
140 }
141
142 return valid;
143 }
144 @Override
145 public boolean processRouteDocument(Document document) {
146 boolean ret = super.processRouteDocument(document);
147 MissedPunchDocument mpDoc = (MissedPunchDocument)document;
148 if(!validateTimeSheet(mpDoc)) {
149 return false;
150 }
151 ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(mpDoc.getPrincipalId());
152 ret &= validateClockAction(mpDoc, lastClock);
153 try {
154 ret &= validateClockTime(mpDoc, lastClock);
155 } catch (ParseException e) {
156 // TODO Auto-generated catch block
157 e.printStackTrace();
158 }
159
160 return ret;
161 }
162 }