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 org.apache.commons.lang.StringUtils;
19  import org.joda.time.DateTime;
20  import org.joda.time.LocalTime;
21  import org.kuali.hr.time.assignment.Assignment;
22  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
23  import org.kuali.hr.time.clocklog.ClockLog;
24  import org.kuali.hr.time.service.base.TkServiceLocator;
25  import org.kuali.hr.time.timesheet.TimesheetDocument;
26  import org.kuali.hr.time.util.TKUtils;
27  import org.kuali.hr.time.util.TkConstants;
28  import org.kuali.rice.kew.api.KewApiServiceLocator;
29  import org.kuali.rice.kew.api.document.DocumentStatus;
30  import org.kuali.rice.krad.document.Document;
31  import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
32  import org.kuali.rice.krad.util.GlobalVariables;
33  
34  import java.text.ParseException;
35  import java.util.HashSet;
36  import java.util.Set;
37  
38  public class MissedPunchValidation extends TransactionalDocumentRuleBase {
39  	
40  	@Override
41  	protected boolean processCustomRouteDocumentBusinessRules(Document document) {
42          boolean valid = true;
43          
44          MissedPunchDocument missedPunchDocument = (MissedPunchDocument) document;
45          DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(missedPunchDocument.getDocumentNumber());
46          
47          if (DocumentStatus.INITIATED.equals(DocumentStatus.fromCode(documentStatus.getCode()))
48          		|| DocumentStatus.SAVED.equals(DocumentStatus.fromCode(documentStatus.getCode()))) {
49  	        valid &= validateTimeSheet(missedPunchDocument);
50              valid &= validateAssignment(missedPunchDocument);
51  
52  	        if (valid) {
53  	        	ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(missedPunchDocument.getPrincipalId());
54  		        try {
55  		        	valid &= validateClockAction(missedPunchDocument, lastClock);
56  		        	valid &= validateClockTime(missedPunchDocument, lastClock);
57  				} catch (ParseException e) {
58  					e.printStackTrace();
59  				}
60  	        }
61          }
62  	    
63          return valid;
64  	}
65  	
66      // do not allow a missed punch if the time sheet document is enroute or final
67      boolean validateTimeSheet(MissedPunchDocument mp) {
68      	boolean valid = true;
69      	TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
70      	if(tsd != null 
71      			&& (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 
72      					|| tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
73      		GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
74      		valid = false;
75      	}
76      	
77      	return valid;
78      }
79  
80      /**
81       * Checks the provided MissedPunch for a valid ClockAction.
82       *
83       * @param mp The MissedPunch we are validating.
84       * @param lastClock The ClockLog entry that happened before the one we want to create.
85       *
86       * @return true if valid, false otherwise.
87       */
88      boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
89          boolean valid = true;
90          Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
91  
92  
93          if (mp.getClockAction().equals(TkConstants.CLOCK_OUT) || mp.getClockAction().equals(TkConstants.LUNCH_OUT)) {
94              ClockLog lci = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.CLOCK_IN); //last clock in
95              ClockLog lli = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.LUNCH_IN); //last lunch in
96              if (lci != null) {
97                  MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lci.getTkClockLogId());
98                  if(mpd != null) {
99                      GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
100                     return false;
101                 }
102             } else if(lli != null) {
103                 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lli.getTkClockLogId());
104                 if(mpd != null) {
105                     GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
106                     return false;
107                 }
108             }
109         }
110 
111         // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut
112         // missed punch can only be used on a tiemblock once.
113 //        if(lastClock != null
114 //        		&& (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) {
115 //        	MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId());
116 //        	if(mpd != null) {
117 //	       	 	GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
118 //	            return false;
119 //        	}
120 //        }
121         if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) {
122             GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action");
123             valid = false;
124         }
125         
126         return valid;
127     }
128 
129     /**
130      * Checks whether the chosen assignment is valid (both chosen and within proper timeframe).
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     /**
183      * Checks whether the chosen assignment is valid (both chosen and within proper timeframe).
184      *
185      * @param mp The MissedPunch we are validating.
186      *
187      * @return true if valid, false otherwise.
188      * @throws ParseException
189      */
190     boolean validateAssignment(MissedPunchDocument mp) {
191         boolean valid = true;
192 
193         String assignment = mp.getAssignment();
194 
195         if (mp.getAssignment() == null) {
196             GlobalVariables.getMessageMap().putError("document.assignment", "clock.mp.assignment.required");
197             valid = false;
198         }
199 
200         if (valid)
201         {
202             AssignmentDescriptionKey key = new AssignmentDescriptionKey(assignment);
203             if (key != null) {
204                 Assignment assignmentObj = TkServiceLocator.getAssignmentService().getAssignment(mp.getPrincipalId(), key, new java.sql.Date(mp.getActionDate().getTime()));
205                 if (assignmentObj == null) {
206                     GlobalVariables.getMessageMap().putError("document.assignment", "clock.mp.assignment.active");
207                     valid = false;
208                 }
209             }
210             else {
211                 valid = false;
212             }
213         }
214 
215         return valid;
216     }
217 }