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.clock.web;
17  
18  import java.math.BigDecimal;
19  import java.sql.Timestamp;
20  import java.text.SimpleDateFormat;
21  import java.util.ArrayList;
22  import java.util.Date;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.servlet.http.HttpServletRequest;
28  import javax.servlet.http.HttpServletResponse;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.apache.log4j.Logger;
32  import org.apache.struts.action.ActionForm;
33  import org.apache.struts.action.ActionForward;
34  import org.apache.struts.action.ActionMapping;
35  import org.joda.time.DateTime;
36  import org.joda.time.DateTimeZone;
37  import org.joda.time.Interval;
38  import org.json.simple.JSONArray;
39  import org.json.simple.JSONValue;
40  import org.kuali.hr.time.assignment.Assignment;
41  import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
42  import org.kuali.hr.time.clocklog.ClockLog;
43  import org.kuali.hr.time.collection.rule.TimeCollectionRule;
44  import org.kuali.hr.time.roles.TkUserRoles;
45  import org.kuali.hr.time.roles.UserRoles;
46  import org.kuali.hr.time.service.base.TkServiceLocator;
47  import org.kuali.hr.time.timeblock.TimeBlock;
48  import org.kuali.hr.time.timesheet.TimesheetDocument;
49  import org.kuali.hr.time.timesheet.web.TimesheetAction;
50  import org.kuali.hr.time.util.TKContext;
51  import org.kuali.hr.time.util.TKUser;
52  import org.kuali.hr.time.util.TKUtils;
53  import org.kuali.hr.time.util.TkConstants;
54  import org.kuali.rice.krad.exception.AuthorizationException;
55  import org.kuali.rice.krad.util.GlobalVariables;
56  
57  public class ClockAction extends TimesheetAction {
58  
59      private static final Logger LOG = Logger.getLogger(ClockAction.class);
60      public static final SimpleDateFormat SDF = new SimpleDateFormat("EEE, MMMM d yyyy HH:mm:ss, zzzz");
61      public static final String SEPERATOR = "[****]+";
62  
63      @Override
64      protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
65          super.checkTKAuthorization(form, methodToCall); // Checks for read access first.
66  
67          UserRoles roles = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId());
68          TimesheetDocument doc = TKContext.getCurrentTimesheetDocument();
69  
70          // Check for write access to Timeblock.
71          if (StringUtils.equals(methodToCall, "clockAction") ||
72                  StringUtils.equals(methodToCall, "addTimeBlock") ||
73                  StringUtils.equals(methodToCall, "editTimeBlock") ||
74                  StringUtils.equals(methodToCall, "distributeTimeBlocks") ||
75                  StringUtils.equals(methodToCall, "saveNewTimeBlocks") ||
76                  StringUtils.equals(methodToCall, "deleteTimeBlock")) {
77              if (!roles.isDocumentWritable(doc)) {
78                  throw new AuthorizationException(roles.getPrincipalId(), "ClockAction", "");
79              }
80          }
81      }
82  
83  
84      @Override
85      public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
86          ActionForward forward = super.execute(mapping, form, request, response);
87          ClockActionForm caf = (ClockActionForm) form;
88          caf.setCurrentServerTime(String.valueOf(new Date().getTime()));
89          caf.getUserSystemOffsetServerTime();
90          caf.setShowLunchButton(TkServiceLocator.getSystemLunchRuleService().isShowLunchButton());
91          caf.setAssignmentDescriptions(TkServiceLocator.getAssignmentService().getAssignmentDescriptions(caf.getTimesheetDocument(), true));
92          if (caf.isShowLunchButton()) {
93              // We don't need to worry about the assignments and lunch rules
94              // if the global lunch rule is turned off.
95  
96              // Check for presence of department lunch rule.
97              Map<String, Boolean> assignmentDeptLunchRuleMap = new HashMap<String, Boolean>();
98              if (caf.getTimesheetDocument() != null) {
99                  for (Assignment a : caf.getTimesheetDocument().getAssignments()) {
100                     String key = AssignmentDescriptionKey.getAssignmentKeyString(a);
101                     assignmentDeptLunchRuleMap.put(key, a.getDeptLunchRule() != null);
102                 }
103             }
104             caf.setAssignmentLunchMap(assignmentDeptLunchRuleMap);
105         }
106         String principalId = TKUser.getCurrentTargetPersonId();
107         if (principalId != null) {
108             caf.setPrincipalId(principalId);
109         }
110 
111         //if there is no timesheet
112         if(caf.getTimesheetDocument() == null) {
113             //don't bother printing this message if we already have an error
114             if (!GlobalVariables.getMessageMap().hasErrors()) {
115                 caf.setErrorMessage("You do not currently have a timesheet. Clock action is not allowed.");
116             }
117             return mapping.findForward("basic");
118         }
119         //if the timesheet document is enroute aor final, don't allow clock action
120         if(caf.getTimesheetDocument().getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)
121                 || caf.getTimesheetDocument().getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL)) {
122             caf.setErrorMessage("Your current timesheet is already submitted for Approval. Clock action is not allowed on this timesheet.");
123             return mapping.findForward("basic");
124         }
125 
126 
127         this.assignShowDistributeButton(caf);
128         // if the time sheet document is final or enroute, do not allow missed punch
129         if(caf.getTimesheetDocument().getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE)
130         		|| caf.getTimesheetDocument().getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL)) {
131         	caf.setShowMissedPunchButton(false);
132         } else {
133         	caf.setShowMissedPunchButton(true);
134         }
135 
136         String tbIdString = caf.getEditTimeBlockId();
137         if (tbIdString != null) {
138             caf.setCurrentTimeBlock(TkServiceLocator.getTimeBlockService().getTimeBlock(caf.getEditTimeBlockId()));
139         }
140 
141         ClockLog lastClockLog = TkServiceLocator.getClockLogService().getLastClockLog(principalId);
142         if (lastClockLog != null) {
143             Timestamp lastClockTimestamp = lastClockLog.getClockTimestamp();
144             String lastClockZone = lastClockLog.getClockTimestampTimezone();
145             if (StringUtils.isEmpty(lastClockZone)) {
146                 lastClockZone = TKUtils.getSystemTimeZone();
147             }
148             // zone will not be null. At this point is Valid or Exception.
149             // Exception would indicate bad data stored in the system. We can wrap this, but
150             // for now, the thrown exception is probably more valuable.
151             DateTimeZone zone = DateTimeZone.forID(lastClockZone);
152             DateTime clockWithZone = new DateTime(lastClockTimestamp, zone);
153             caf.setLastClockTimeWithZone(clockWithZone.toDate());
154             caf.setLastClockTimestamp(lastClockTimestamp);
155             caf.setLastClockAction(lastClockLog.getClockAction());
156         }
157 
158         if (lastClockLog == null || StringUtils.equals(lastClockLog.getClockAction(), TkConstants.CLOCK_OUT)) {
159             caf.setCurrentClockAction(TkConstants.CLOCK_IN);
160         } else {
161 
162             if (StringUtils.equals(lastClockLog.getClockAction(), TkConstants.LUNCH_OUT) && TkServiceLocator.getSystemLunchRuleService().isShowLunchButton()) {
163                 caf.setCurrentClockAction(TkConstants.LUNCH_IN);
164             }
165 //	   	    	else if(StringUtils.equals(lastClockLog.getClockAction(),TkConstants.LUNCH_OUT)) {
166 //	   	    		caf.setCurrentClockAction(TkConstants.LUNCH_IN);
167 //	   	    	}
168             else {
169                 caf.setCurrentClockAction(TkConstants.CLOCK_OUT);
170             }
171             // if the current clock action is clock out, displays only the clocked-in assignment
172             String selectedAssignment = new AssignmentDescriptionKey(lastClockLog.getJobNumber(), lastClockLog.getWorkArea(), lastClockLog.getTask()).toAssignmentKeyString();
173             caf.setSelectedAssignment(selectedAssignment);
174             Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(caf.getTimesheetDocument(), selectedAssignment);
175             Map<String, String> assignmentDesc = TkServiceLocator.getAssignmentService().getAssignmentDescriptions(assignment);
176             caf.setAssignmentDescriptions(assignmentDesc);
177 
178         }
179         
180 
181         
182         if (StringUtils.equals(GlobalVariables.getUserSession().getPrincipalId(), TKUser.getCurrentTargetPersonId())) {
183         	caf.setClockButtonEnabled(true);
184         } else {
185             boolean isApproverOrReviewerForCurrentAssignment = false;
186             String selectedAssignment = StringUtils.EMPTY;
187             if (caf.getAssignmentDescriptions() != null) {
188 	            if (caf.getAssignmentDescriptions().size() == 1) {
189 	            	for (String assignment : caf.getAssignmentDescriptions().keySet()) {
190 	            		selectedAssignment = assignment;
191 	            	}
192 	            } else {
193 	            	selectedAssignment = caf.getSelectedAssignment();
194 	            }
195             }
196             if (StringUtils.isNotBlank(selectedAssignment)) {
197             	Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(new AssignmentDescriptionKey(selectedAssignment), TKUtils.getCurrentDate());
198             	if (assignment != null) {
199                     UserRoles roles = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId());
200                     Long workArea = assignment.getWorkArea();
201                     isApproverOrReviewerForCurrentAssignment = roles.getApproverWorkAreas().contains(workArea) || roles.getReviewerWorkAreas().contains(workArea);
202             	}
203             }
204         	caf.setClockButtonEnabled(isApproverOrReviewerForCurrentAssignment);
205         }
206         
207         return forward;
208     }
209     
210     public void assignShowDistributeButton(ClockActionForm caf) {
211     	caf.setShowDistrubuteButton(false);
212     	
213     	TimesheetDocument timesheetDocument = caf.getTimesheetDocument();
214     	if (timesheetDocument != null) {
215     		int eligibleAssignmentCount = 0;
216     		for (Assignment assignment : timesheetDocument.getAssignments()) {
217     			String department = assignment.getJob() != null ? assignment.getJob().getDept() : null;
218     			Long workArea = assignment.getWorkArea();
219     			String payType = assignment.getJob() != null ? assignment.getJob().getHrPayType() : null;
220     			TimeCollectionRule rule = TkServiceLocator.getTimeCollectionRuleService().getTimeCollectionRule(department, workArea, payType, timesheetDocument.getDocEndDate());
221 		    	if (rule != null && rule.isHrsDistributionF()) {
222 		    		eligibleAssignmentCount++;
223 		    	}
224 		    	
225 		    	// Only show the distribute button if there is more than one eligible assignment
226 		    	if (eligibleAssignmentCount > 1) {
227 		    		caf.setShowDistrubuteButton(true);
228 		    		break;
229 		    	}
230     		}
231     	}
232     }
233     
234 
235     public ActionForward clockAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
236         ClockActionForm caf = (ClockActionForm) form;
237 
238         // TODO: Validate that clock action is valid for this user
239         // TODO: this needs to be integrated with the error tag
240         if (StringUtils.isBlank(caf.getSelectedAssignment())) {
241             caf.setErrorMessage("No assignment selected.");
242             return mapping.findForward("basic");
243         }
244         ClockLog previousClockLog = TkServiceLocator.getClockLogService().getLastClockLog(TKUser.getCurrentTargetPersonId());
245         if(previousClockLog != null && StringUtils.equals(caf.getCurrentClockAction(), previousClockLog.getClockAction())){
246         	caf.setErrorMessage("The operation is already performed.");
247             return mapping.findForward("basic");
248         }
249         String ip = TKUtils.getIPAddressFromRequest(request);
250         Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(caf.getTimesheetDocument(), caf.getSelectedAssignment());
251         
252         List<Assignment> lstAssingmentAsOfToday = TkServiceLocator.getAssignmentService().getAssignments(TKContext.getTargetPrincipalId(), TKUtils.getCurrentDate());
253         boolean foundValidAssignment = false;
254         for(Assignment assign : lstAssingmentAsOfToday){
255         	if((assign.getJobNumber().compareTo(assignment.getJobNumber()) ==0) &&
256         		(assign.getWorkArea().compareTo(assignment.getWorkArea()) == 0) &&
257         		(assign.getTask().compareTo(assignment.getTask()) == 0)){
258         		foundValidAssignment = true;
259         		break;
260         	}
261         }
262         
263         if(!foundValidAssignment){
264         	caf.setErrorMessage("Assignment is not effective as of today");
265         	return mapping.findForward("basic");
266         }
267         
268                
269         ClockLog clockLog = TkServiceLocator.getClockLogService().processClockLog(new Timestamp(System.currentTimeMillis()), assignment, caf.getPayCalendarDates(), ip,
270                 TKUtils.getCurrentDate(), caf.getTimesheetDocument(), caf.getCurrentClockAction(), true, TKUser.getCurrentTargetPersonId());
271 
272         caf.setClockLog(clockLog);
273 
274         return mapping.findForward("basic");
275     }
276 
277     public ActionForward distributeTimeBlocks(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
278         ClockActionForm caf = (ClockActionForm) form;
279         caf.findTimeBlocksToDistribute();
280         return mapping.findForward("tb");
281     }
282 
283 
284     public ActionForward editTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
285         ClockActionForm caf = (ClockActionForm) form;
286         TimeBlock tb = caf.getCurrentTimeBlock();
287         caf.setCurrentAssignmentKey(tb.getAssignmentKey());
288 
289         ActionForward forward = mapping.findForward("et");
290 
291         return new ActionForward(forward.getPath() + "?editTimeBlockId=" + tb.getTkTimeBlockId().toString());
292 
293     }
294     public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
295         ClockActionForm caf = (ClockActionForm) form;
296         TimeBlock currentTb = caf.getCurrentTimeBlock();
297         List<TimeBlock> newTimeBlocks = caf.getTimesheetDocument().getTimeBlocks();
298         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(caf.getTimesheetDocument().getTimeBlocks().size());
299         for (TimeBlock tb : caf.getTimesheetDocument().getTimeBlocks()) {
300             referenceTimeBlocks.add(tb.copy());
301         }
302         //call persist method that only saves added/deleted/changed timeblocks
303         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, TKContext.getPrincipalId());
304 
305         ActionForward forward = mapping.findForward("et");
306 
307         return new ActionForward(forward.getPath() + "?editTimeBlockId=" + currentTb.getTkTimeBlockId().toString());
308     }
309     
310     public ActionForward saveNewTimeBlocks(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){
311 		ClockActionForm caf = (ClockActionForm)form;
312 		String tbId = caf.getTbId();
313 		String timesheetDocId = caf.getTsDocId();
314 
315 		String[] assignments = caf.getNewAssignDesCol().split(SEPERATOR);
316 		String[] beginDates = caf.getNewBDCol().split(SEPERATOR);
317 		String[] beginTimes = caf.getNewBTCol().split(SEPERATOR);
318 		String[] endDates = caf.getNewEDCol().split(SEPERATOR);
319 		String[] endTimes = caf.getNewETCol().split(SEPERATOR);
320 		String[] hrs = caf.getNewHrsCol().split(SEPERATOR);
321 		String earnCode = TkServiceLocator.getTimeBlockService().getTimeBlock(tbId).getEarnCode();
322 
323 		List<TimeBlock> newTbList = new ArrayList<TimeBlock>();
324 		for(int i = 0; i < hrs.length; i++) {
325 			BigDecimal hours = new BigDecimal(hrs[i]);
326 			Timestamp beginTS = TKUtils.convertDateStringToTimestamp(beginDates[i], beginTimes[i]);
327 			Timestamp endTS = TKUtils.convertDateStringToTimestamp(endDates[i], endTimes[i]);
328 			String assignString = assignments[i];
329 			Assignment assignment = TkServiceLocator.getAssignmentService().getAssignment(assignString);
330 			
331 			TimesheetDocument tsDoc = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocId);
332 			
333 			TimeBlock tb = TkServiceLocator.getTimeBlockService().createTimeBlock(tsDoc, beginTS, endTS, assignment, earnCode, hours,BigDecimal.ZERO, false, false, TKContext.getPrincipalId());
334 			newTbList.add(tb);
335 		}
336 		TkServiceLocator.getTimeBlockService().resetTimeHourDetail(newTbList);
337 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTbList);
338 		TimeBlock oldTB = TkServiceLocator.getTimeBlockService().getTimeBlock(tbId);
339 		TkServiceLocator.getTimeBlockService().deleteTimeBlock(oldTB);
340 		return mapping.findForward("basic");
341 	}
342 	
343 	public ActionForward validateNewTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){
344 		ClockActionForm caf = (ClockActionForm)form;
345 		String tbId = caf.getTbId();
346 		String[] assignments = caf.getNewAssignDesCol().split(SEPERATOR);
347 		String[] beginDates = caf.getNewBDCol().split(SEPERATOR);
348 		String[] beginTimes = caf.getNewBTCol().split(SEPERATOR);
349 		String[] endDates = caf.getNewEDCol().split(SEPERATOR);
350 		String[] endTimes = caf.getNewETCol().split(SEPERATOR);
351 		String[] hrs = caf.getNewHrsCol().split(SEPERATOR);
352 
353 		List<Interval> newIntervals = new ArrayList<Interval>();
354 		JSONArray errorMsgList = new JSONArray();
355 
356 		// validates that all fields are available
357 		if(assignments.length != beginDates.length ||
358 				assignments.length!= beginTimes.length ||
359 				assignments.length != endDates.length ||
360 				assignments.length != endTimes.length ||
361 				assignments.length != hrs.length) {
362 			errorMsgList.add("All fields are required");
363 		    caf.setOutputString(JSONValue.toJSONString(errorMsgList));
364 		    return mapping.findForward("ws");
365 		}
366 
367 		for(int i = 0; i < hrs.length; i++) {
368 			String index = String.valueOf(i+1);
369 
370 			// validate the hours field
371 			BigDecimal dc = new BigDecimal(hrs[i]);
372 		    if (dc.compareTo(new BigDecimal("0")) == 0) {
373 		        errorMsgList.add("The entered hours for entry " + index + " is not valid.");
374 		        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
375 		        return mapping.findForward("ws");
376 		    }
377 
378 		    // check if the begin / end time are valid
379 		    // should not include time zone in consideration when conparing time intervals
380 		    Timestamp beginTS = TKUtils.convertDateStringToTimestampWithoutZone(beginDates[i], beginTimes[i]);
381 			Timestamp endTS = TKUtils.convertDateStringToTimestampWithoutZone(endDates[i], endTimes[i]);
382 		    if ((beginTS.compareTo(endTS) > 0 || endTS.compareTo(beginTS) < 0)) {
383 		        errorMsgList.add("The time or date for entry " + index + " is not valid.");
384 		        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
385 		        return mapping.findForward("ws");
386 		    }
387 
388 		    // check if new time blocks overlap with existing time blocks
389 		    DateTime start = new DateTime(beginTS);
390 		    DateTime end = new DateTime(endTS);
391 		    Interval addedTimeblockInterval = new Interval(start, end);
392 		    newIntervals.add(addedTimeblockInterval);
393 		    for (TimeBlock timeBlock : caf.getTimesheetDocument().getTimeBlocks()) {
394 		    	if(timeBlock.getTkTimeBlockId().equals(tbId)) {	// ignore the original time block
395 		    		continue;
396 		    	}
397 		    	if(timeBlock.getHours().compareTo(BigDecimal.ZERO) == 0) { // ignore time blocks with zero hours
398 		    		continue;
399 		    	}
400 		    	
401 		    	DateTimeZone dateTimeZone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
402 		    	DateTime timeBlockBeginTimestamp = new DateTime(timeBlock.getBeginTimestamp().getTime(), dateTimeZone).withZoneRetainFields(TKUtils.getSystemDateTimeZone());
403 		    	DateTime timeBlockEndTimestamp = new DateTime(timeBlock.getEndTimestamp().getTime(), dateTimeZone).withZoneRetainFields(TKUtils.getSystemDateTimeZone());
404 			    Interval timeBlockInterval = new Interval(timeBlockBeginTimestamp, timeBlockEndTimestamp);
405 			    if (timeBlockInterval.overlaps(addedTimeblockInterval)) {
406 			        errorMsgList.add("The time block you are trying to add for entry " + index + " overlaps with an existing time block.");
407 			        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
408 			        return mapping.findForward("ws");
409 			    }
410 		    }
411 		}
412 		// check if new time blocks overlap with each other
413 		if(newIntervals.size() > 1 ) {
414 			for(Interval intv1 : newIntervals) {
415 				for(Interval intv2 : newIntervals) {
416 					if(intv1.equals(intv2)) {
417 						continue;
418 					}
419 					if (intv1.overlaps(intv2)) {
420 						errorMsgList.add("There is time overlap between the entries.");
421 				        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
422 				        return mapping.findForward("ws");
423 					}
424 				}
425 			}
426 		}
427 
428 	    caf.setOutputString(JSONValue.toJSONString(errorMsgList));
429 		return mapping.findForward("ws");
430  	}
431 
432     public ActionForward closeMissedPunchDoc(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){
433         return mapping.findForward("closeMissedPunchDoc");
434     }
435     
436 }