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.List;
24  import java.util.Set;
25  
26  import org.apache.commons.lang.StringUtils;
27  import org.joda.time.DateTime;
28  import org.joda.time.DateTimeZone;
29  import org.joda.time.LocalTime;
30  import org.kuali.hr.time.assignment.Assignment;
31  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
32  import org.kuali.hr.time.clocklog.ClockLog;
33  import org.kuali.hr.time.service.base.TkServiceLocator;
34  import org.kuali.hr.time.timesheet.TimesheetDocument;
35  import org.kuali.hr.time.util.TKContext;
36  import org.kuali.hr.time.util.TKUtils;
37  import org.kuali.hr.time.util.TkConstants;
38  import org.kuali.rice.kew.api.KewApiServiceLocator;
39  import org.kuali.rice.kew.api.document.DocumentStatus;
40  import org.kuali.rice.krad.document.Document;
41  import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
42  import org.kuali.rice.krad.util.GlobalVariables;
43  
44  public class MissedPunchValidation extends TransactionalDocumentRuleBase {
45  	
46  	@Override
47  	protected boolean processCustomRouteDocumentBusinessRules(Document document) {
48          boolean valid = true;
49          
50          MissedPunchDocument missedPunchDocument = (MissedPunchDocument) document;
51          DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(missedPunchDocument.getDocumentNumber());
52          
53          if (DocumentStatus.INITIATED.equals(DocumentStatus.fromCode(documentStatus.getCode()))
54          		|| DocumentStatus.SAVED.equals(DocumentStatus.fromCode(documentStatus.getCode()))) {
55  	        valid &= validateTimeSheet(missedPunchDocument);
56              valid &= validateAssignment(missedPunchDocument);
57  
58  	        if (valid) {
59  	        	ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(missedPunchDocument.getPrincipalId());
60  		        try {
61  		        	valid &= validateClockAction(missedPunchDocument, lastClock);
62  		        	valid &= validateClockTime(missedPunchDocument, lastClock);
63  				} catch (ParseException e) {
64  					e.printStackTrace();
65  				}
66  	        }
67          }
68  	    
69          return valid;
70  	}
71  	
72      // do not allow a missed punch if the time sheet document is enroute or final
73      boolean validateTimeSheet(MissedPunchDocument mp) {
74      	boolean valid = true;
75      	TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
76      	if(tsd != null 
77      			&& (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 
78      					|| tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
79      		GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
80      		valid = false;
81      	}
82      	
83      	return valid;
84      }
85  
86      /**
87       * Checks the provided MissedPunch for a valid ClockAction.
88       *
89       * @param mp The MissedPunch we are validating.
90       * @param lastClock The ClockLog entry that happened before the one we want to create.
91       *
92       * @return true if valid, false otherwise.
93       */
94      boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
95          boolean valid = true;
96          Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
97  
98  
99          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 }