View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.time.missedpunch;
17  
18  import java.sql.Timestamp;
19  import java.text.ParseException;
20  import java.text.SimpleDateFormat;
21  import java.util.Date;
22  import java.util.HashSet;
23  import java.util.Set;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.joda.time.DateTime;
27  import org.joda.time.DateTimeZone;
28  import org.joda.time.LocalTime;
29  import org.kuali.hr.time.clocklog.ClockLog;
30  import org.kuali.hr.time.service.base.TkServiceLocator;
31  import org.kuali.hr.time.timesheet.TimesheetDocument;
32  import org.kuali.hr.time.util.TKUtils;
33  import org.kuali.hr.time.util.TkConstants;
34  import org.kuali.rice.krad.document.Document;
35  import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
36  import org.kuali.rice.krad.util.GlobalVariables;
37  
38  public class MissedPunchValidation extends TransactionalDocumentRuleBase {
39  
40      /**
41       * Checks the provided MissedPunch for a valid ClockAction.
42       *
43       * @param mp The MissedPunch we are validating.
44       * @param lastClock The ClockLog entry that happened before the one we want to create.
45       *
46       * @return true if valid, false otherwise.
47       */
48      boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
49          boolean valid = true;
50          Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
51  
52          // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut
53          // missed punch can only be used on a tiemblock once.
54          if(lastClock != null 
55          		&& (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) {
56          	MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId());
57          	if(mpd != null) {
58  	       	 	GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
59  	            return false;
60          	}
61          }
62          if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) {
63              GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action");
64              valid = false;
65          }
66          
67          return valid;
68      }
69  
70      /**
71       * Checks whether the provided MissedPunch has a valid time based on the
72       * previous ClockLog.
73       *
74       * @param mp The MissedPunch we are validating.
75       * @param lastClock The ClockLog entry that happened before the one we want to create.
76       *
77       * @return true if valid, false otherwise.
78       * @throws ParseException 
79       */
80      boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException {
81          boolean valid = true;
82  
83          if (lastClock == null)
84              return valid;
85          
86          //Missed Action Date and Missed Action Time are required fields. KPME-1853
87          if(mp.getActionTime() == null || mp.getActionDate() == null)
88          	return false;
89          
90          DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime());
91          DateTime boundaryMax = clockLogDateTime.plusDays(1);
92          DateTime nowTime = new DateTime(TKUtils.getCurrentDate());
93          
94          SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
95          String s = formatter.format(mp.getActionDate());
96  		Date tempDate = formatter.parse(s);
97  		Timestamp dateLocal = new Timestamp(tempDate.getTime());
98          LocalTime timeLocal = new LocalTime(mp.getActionTime().getTime());
99          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 }