001 /**
002 * Copyright 2004-2012 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 return valid;
067 }
068
069 /**
070 * Checks whether the provided MissedPunch has a valid time based on the
071 * previous ClockLog.
072 *
073 * @param mp The MissedPunch we are validating.
074 * @param lastClock The ClockLog entry that happened before the one we want to create.
075 *
076 * @return true if valid, false otherwise.
077 * @throws ParseException
078 */
079 boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException {
080 boolean valid = true;
081
082 if (lastClock == null)
083 return valid;
084
085 DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime());
086 DateTime boundaryMax = clockLogDateTime.plusDays(1);
087 DateTime nowTime = new DateTime(TKUtils.getCurrentDate());
088
089 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
090 String s = formatter.format(mp.getActionDate());
091 Date tempDate = formatter.parse(s);
092 Timestamp dateLocal = new Timestamp(tempDate.getTime());
093 LocalTime timeLocal = new LocalTime(mp.getActionTime().getTime());
094 DateTime actionDateTime = new DateTime(dateLocal.getTime());
095 actionDateTime = actionDateTime.plus(timeLocal.getMillisOfDay());
096
097 // convert the action time to the system zone
098 Timestamp ts = new Timestamp(actionDateTime.getMillis());
099 ClockLog lastLog = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId());
100 Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastLog.getClockTimestampTimezone()));
101 Timestamp actionTime = new Timestamp(ts.getTime()-zoneOffset);
102 DateTime newDateTime = new DateTime(actionTime.getTime());
103
104 // if date is a future date
105 if(actionDateTime.getYear()> nowTime.getYear()
106 || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) {
107 GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date");
108 return false;
109 }
110
111 // if time is a future time
112 if(actionDateTime.getMillis() > nowTime.getMillis()) {
113 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time");
114 return false;
115 }
116
117
118 if ( ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax))
119 || newDateTime.isBefore(clockLogDateTime)) && StringUtils.equals(mp.getDocumentStatus(),"R")) {
120 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime");
121 valid = false;
122 }
123
124 return valid;
125 }
126
127 // do not allow a missed punch is the time sheet document is enroute or final
128 boolean validateTimeSheet(MissedPunchDocument mp) {
129 boolean valid = true;
130 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
131 if(tsd != null
132 && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)
133 || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
134 GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
135 valid = false;
136 }
137 return valid;
138 }
139 @Override
140 public boolean processRouteDocument(Document document) {
141 boolean ret = super.processRouteDocument(document);
142 MissedPunchDocument mpDoc = (MissedPunchDocument)document;
143 if(!validateTimeSheet(mpDoc)) {
144 return false;
145 }
146 ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(mpDoc.getPrincipalId());
147 ret &= validateClockAction(mpDoc, lastClock);
148 try {
149 ret &= validateClockTime(mpDoc, lastClock);
150 } catch (ParseException e) {
151 // TODO Auto-generated catch block
152 e.printStackTrace();
153 }
154
155 return ret;
156 }
157 }