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.kew.api.KewApiServiceLocator;
35  import org.kuali.rice.kew.api.document.DocumentStatus;
36  import org.kuali.rice.krad.document.Document;
37  import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
38  import org.kuali.rice.krad.util.GlobalVariables;
39  
40  public class MissedPunchValidation extends TransactionalDocumentRuleBase {
41  	
42  	@Override
43  	protected boolean processCustomRouteDocumentBusinessRules(Document document) {
44          boolean valid = true;
45          
46          MissedPunchDocument missedPunchDocument = (MissedPunchDocument) document;
47          DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(missedPunchDocument.getDocumentNumber());
48          
49          if (DocumentStatus.INITIATED.equals(DocumentStatus.fromCode(documentStatus.getCode()))
50          		|| DocumentStatus.SAVED.equals(DocumentStatus.fromCode(documentStatus.getCode()))) {
51  	        valid &= validateTimeSheet(missedPunchDocument);
52  	        
53  	        if (valid) {
54  	        	ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(missedPunchDocument.getPrincipalId());
55  		        try {
56  		        	valid &= validateClockAction(missedPunchDocument, lastClock);
57  		        	valid &= validateClockTime(missedPunchDocument, lastClock);
58  				} catch (ParseException e) {
59  					e.printStackTrace();
60  				}
61  	        }
62          }
63  	    
64          return valid;
65  	}
66  	
67      // do not allow a missed punch if the time sheet document is enroute or final
68      boolean validateTimeSheet(MissedPunchDocument mp) {
69      	boolean valid = true;
70      	TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
71      	if(tsd != null 
72      			&& (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 
73      					|| tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
74      		GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
75      		valid = false;
76      	}
77      	
78      	return valid;
79      }
80  
81      /**
82       * Checks the provided MissedPunch for a valid ClockAction.
83       *
84       * @param mp The MissedPunch we are validating.
85       * @param lastClock The ClockLog entry that happened before the one we want to create.
86       *
87       * @return true if valid, false otherwise.
88       */
89      boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
90          boolean valid = true;
91          Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
92  
93  
94          if (mp.getClockAction().equals(TkConstants.CLOCK_OUT) || mp.getClockAction().equals(TkConstants.LUNCH_OUT)) {
95              ClockLog lci = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.CLOCK_IN); //last clock in
96              ClockLog lli = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.LUNCH_IN); //last lunch in
97              if (lci != null) {
98                  MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lci.getTkClockLogId());
99                  if(mpd != null) {
100                     GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
101                     return false;
102                 }
103             } else if(lli != null) {
104                 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lli.getTkClockLogId());
105                 if(mpd != null) {
106                     GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
107                     return false;
108                 }
109             }
110         }
111 
112         // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut
113         // missed punch can only be used on a tiemblock once.
114 //        if(lastClock != null
115 //        		&& (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) {
116 //        	MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId());
117 //        	if(mpd != null) {
118 //	       	 	GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
119 //	            return false;
120 //        	}
121 //        }
122         if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) {
123             GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action");
124             valid = false;
125         }
126         
127         return valid;
128     }
129 
130     /**
131      * Checks whether the provided MissedPunch has a valid time based on the
132      * previous ClockLog.
133      *
134      * @param mp The MissedPunch we are validating.
135      * @param lastClock The ClockLog entry that happened before the one we want to create.
136      *
137      * @return true if valid, false otherwise.
138      * @throws ParseException 
139      */
140     boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException {
141         boolean valid = true;
142 
143         if (lastClock == null) {
144             return valid;
145         }
146         
147         //Missed Action Date and Missed Action Time are required fields. KPME-1853
148         if(mp.getActionTime() == null || mp.getActionDate() == null)
149         	return false;
150         
151         DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime());
152         DateTime boundaryMax = clockLogDateTime.plusDays(1);
153         DateTime nowTime = new DateTime(TKUtils.getCurrentDate());
154         long offset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback());
155         long dateTimeLocal = new LocalTime(mp.getActionTime()).getMillisOfDay() + mp.getActionDate().getTime() - offset;
156 
157         //this will be in system's timezone, but offset with user's timezone
158         DateTime actionDateTime = new DateTime(dateTimeLocal);
159 
160         // if date is a future date
161         if(actionDateTime.getYear()> nowTime.getYear()
162         		|| (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) {
163         	GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date");
164         	return false;
165         }
166 
167         // if time is a future time
168         if(actionDateTime.getMillis() > nowTime.getMillis()) {
169         	GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time");
170         	return false;
171         }
172         
173         if ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax)) 
174         		|| actionDateTime.isBefore(clockLogDateTime)) {
175         	GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime");
176             valid = false;
177         }
178 
179         return valid;
180     }
181 
182 }