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