View Javadoc

1   /**
2    * Copyright 2004-2014 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.kpme.tklm.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.HashMap;
23  import java.util.HashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Set;
27  
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.http.HttpServletResponse;
30  
31  import org.apache.commons.collections.CollectionUtils;
32  import org.apache.commons.lang.StringUtils;
33  import org.apache.struts.action.ActionForm;
34  import org.apache.struts.action.ActionForward;
35  import org.apache.struts.action.ActionMapping;
36  import org.joda.time.DateTime;
37  import org.joda.time.DateTimeZone;
38  import org.joda.time.Interval;
39  import org.joda.time.LocalDate;
40  import org.json.simple.JSONArray;
41  import org.json.simple.JSONValue;
42  import org.kuali.kpme.core.KPMENamespace;
43  import org.kuali.kpme.core.assignment.Assignment;
44  import org.kuali.kpme.core.assignment.AssignmentDescriptionKey;
45  import org.kuali.kpme.core.calendar.entry.CalendarEntry;
46  import org.kuali.kpme.core.document.calendar.CalendarDocument;
47  import org.kuali.kpme.core.earncode.EarnCode;
48  import org.kuali.kpme.core.role.KPMERole;
49  import org.kuali.kpme.core.service.HrServiceLocator;
50  import org.kuali.kpme.core.util.HrConstants;
51  import org.kuali.kpme.core.util.HrContext;
52  import org.kuali.kpme.core.util.TKUtils;
53  import org.kuali.kpme.core.workarea.WorkArea;
54  import org.kuali.kpme.tklm.common.TkConstants;
55  import org.kuali.kpme.tklm.leave.block.LeaveBlock;
56  import org.kuali.kpme.tklm.time.clocklog.ClockLog;
57  import org.kuali.kpme.tklm.time.rules.lunch.department.DeptLunchRule;
58  import org.kuali.kpme.tklm.time.rules.timecollection.TimeCollectionRule;
59  import org.kuali.kpme.tklm.time.service.TkServiceLocator;
60  import org.kuali.kpme.tklm.time.timeblock.TimeBlock;
61  import org.kuali.kpme.tklm.time.timesheet.TimesheetDocument;
62  import org.kuali.kpme.tklm.time.timesheet.web.TimesheetAction;
63  import org.kuali.kpme.tklm.time.workflow.TimesheetDocumentHeader;
64  import org.kuali.rice.krad.exception.AuthorizationException;
65  import org.kuali.rice.krad.service.KRADServiceLocator;
66  import org.kuali.rice.krad.util.GlobalVariables;
67  import org.springframework.cache.annotation.CacheEvict;
68  
69  public class ClockAction extends TimesheetAction {
70  
71      public static final SimpleDateFormat SDF = new SimpleDateFormat("EEE, MMMM d yyyy HH:mm:ss, zzzz");
72      public static final String SEPERATOR = "[****]+";
73      public static final String DOCUMENT_NOT_INITIATE_ERROR = "New Timesheet document could not be found. Please initiate the document first.";
74      public static final String TIME_BLOCK_OVERLAP_ERROR = "User has already logged time for this clock period.";
75  
76      @Override
77      protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
78          super.checkTKAuthorization(form, methodToCall); // Checks for read access first.
79  
80          ClockActionForm clockActionForm = (ClockActionForm) form;
81  
82          String principalId = GlobalVariables.getUserSession().getPrincipalId();
83      	CalendarDocument timesheetDocument = TkServiceLocator.getTimesheetService().getTimesheetDocument(clockActionForm.getDocumentId());
84          // Check for write access to Timeblock.
85          if (StringUtils.equals(methodToCall, "clockAction") ||
86                  StringUtils.equals(methodToCall, "addTimeBlock") ||
87                  StringUtils.equals(methodToCall, "editTimeBlock") ||
88                  StringUtils.equals(methodToCall, "distributeTimeBlocks") ||
89                  StringUtils.equals(methodToCall, "saveNewTimeBlocks") ||
90                  StringUtils.equals(methodToCall, "deleteTimeBlock")) {
91              if (!HrServiceLocator.getHRPermissionService().canEditCalendarDocument(principalId, timesheetDocument)) {
92                  throw new AuthorizationException(GlobalVariables.getUserSession().getPrincipalId(), "ClockAction", "");
93              }
94          }
95      }
96  
97  
98      @Override
99      public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
100         ActionForward actionForward = super.execute(mapping, form, request, response);
101         
102         ClockActionForm clockActionForm = (ClockActionForm) form;
103         
104         TimesheetDocument timesheetDocument = clockActionForm.getTimesheetDocument();
105         clockActionForm.setShowClockButton(true);
106         if (timesheetDocument != null) {
107 	        if (!timesheetDocument.getDocumentHeader().getDocumentStatus().equals(HrConstants.ROUTE_STATUS.ENROUTE)
108 	                && !timesheetDocument.getDocumentHeader().getDocumentStatus().equals(HrConstants.ROUTE_STATUS.FINAL)) {
109         	
110 		        String targetPrincipalId = HrContext.getTargetPrincipalId();
111 		        if (targetPrincipalId != null) {
112 		            clockActionForm.setPrincipalId(targetPrincipalId);
113 		        }
114 		        clockActionForm.setAssignmentDescriptions(timesheetDocument.getAssignmentDescriptions(true));
115 		        
116 		        if (clockActionForm.getEditTimeBlockId() != null) {
117 		            clockActionForm.setCurrentTimeBlock(TkServiceLocator.getTimeBlockService().getTimeBlock(clockActionForm.getEditTimeBlockId()));
118 		        }
119 		        
120 		        ClockLog lastClockLog = TkServiceLocator.getClockLogService().getLastClockLog(targetPrincipalId);
121 		        if (lastClockLog != null) {
122 		            DateTime lastClockDateTime = lastClockLog.getClockDateTime();
123 		            String lastClockZone = lastClockLog.getClockTimestampTimezone();
124 		            if (StringUtils.isEmpty(lastClockZone)) {
125 		                lastClockZone = TKUtils.getSystemTimeZone();
126 		            }
127 		            // zone will not be null. At this point is Valid or Exception.
128 		            // Exception would indicate bad data stored in the system. We can wrap this, but
129 		            // for now, the thrown exception is probably more valuable.
130 		            DateTimeZone zone = DateTimeZone.forID(lastClockZone);
131 		            DateTime clockWithZone = lastClockDateTime.withZone(zone);
132 		            clockActionForm.setLastClockTimeWithZone(clockWithZone.toDate());
133 		            clockActionForm.setLastClockTimestamp(lastClockDateTime.toDate());
134 		            clockActionForm.setLastClockAction(lastClockLog.getClockAction());
135 		        }
136 		        
137 		        if (lastClockLog == null || StringUtils.equals(lastClockLog.getClockAction(), TkConstants.CLOCK_OUT)) {
138 		            clockActionForm.setCurrentClockAction(TkConstants.CLOCK_IN);
139 		        } else {
140 		            if (StringUtils.equals(lastClockLog.getClockAction(), TkConstants.LUNCH_OUT) && TkServiceLocator.getSystemLunchRuleService().isShowLunchButton()) {
141 		                clockActionForm.setCurrentClockAction(TkConstants.LUNCH_IN);
142 		                clockActionForm.setShowClockButton(false);
143 		            } else {
144 		                clockActionForm.setCurrentClockAction(TkConstants.CLOCK_OUT);
145 		            }
146 		            // if the current clock action is clock out, displays only the clocked-in assignment
147 		            String selectedAssignment = new AssignmentDescriptionKey(lastClockLog.getJobNumber(), lastClockLog.getWorkArea(), lastClockLog.getTask()).toAssignmentKeyString();
148 		            clockActionForm.setSelectedAssignment(selectedAssignment);
149 		            Assignment assignment = timesheetDocument.getAssignment(AssignmentDescriptionKey.get(selectedAssignment));
150 		            Map<String, String> assignmentDesc = HrServiceLocator.getAssignmentService().getAssignmentDescriptions(assignment);
151 		            clockActionForm.setAssignmentDescriptions(assignmentDesc);
152 		        }
153 		        
154 		        // KPME-2772 This issue happens when target employee is clocked out and there are multiple assignments 
155 		        // because when there are more than one assignment, it uses "default" assignment on the clock action form,
156 		        // which never gets set above.  When target employee is clocked in, there is always one assignment, so it works.
157 		        // The solution is to add else if statement and set clockButtonEnabled flag to true when target employee is clocked out.  
158 		        // Since all the assignments for target employee are already filtered by the time it gets here (i.e, only showing the ones
159 		        // that approver has permission to view for), we will just enable buttons.  When target employee is clocked in, it gets
160 		        // handled in else statement
161 		        if (StringUtils.equals(GlobalVariables.getUserSession().getPrincipalId(), HrContext.getTargetPrincipalId())) {
162 		        	clockActionForm.setClockButtonEnabled(true);
163 		        } else {
164 		        	boolean isApproverOrReviewerForCurrentAssignment = false;
165 		        	String selectedAssignment = StringUtils.EMPTY;
166 		        	if (clockActionForm.getAssignmentDescriptions() != null) {
167 		        		if (clockActionForm.getAssignmentDescriptions().size() == 1) {
168 		        			for (String assignment : clockActionForm.getAssignmentDescriptions().keySet()) {
169 		        				selectedAssignment = assignment;
170 		        			}
171 		        		} else {
172 		        			selectedAssignment = clockActionForm.getSelectedAssignment();
173 		        		}
174 		        	}
175 		        	
176 		        	if(StringUtils.isNotBlank(selectedAssignment)) {
177 		        		Assignment assignment = HrServiceLocator.getAssignmentService().getAssignmentForTargetPrincipal(AssignmentDescriptionKey.get(selectedAssignment), LocalDate.now());
178 		        		if (assignment != null) {
179 		        			Long workArea = assignment.getWorkArea();
180                             String dept = assignment.getJob().getDept();
181 		        			String principalId = HrContext.getPrincipalId();
182 		        			DateTime startOfToday = LocalDate.now().toDateTimeAtStartOfDay();
183                             isApproverOrReviewerForCurrentAssignment =
184                                     HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(principalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.APPROVER.getRoleName(), workArea, startOfToday)
185 		        					|| HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(principalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.APPROVER_DELEGATE.getRoleName(), workArea, startOfToday)
186 		        					|| HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(principalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.REVIEWER.getRoleName(), workArea, startOfToday)
187                                     || HrServiceLocator.getKPMERoleService().principalHasRoleInDepartment(principalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR.getRoleName(), dept, startOfToday)
188                                     || HrServiceLocator.getKPMERoleService().principalHasRoleInDepartment(principalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR_DELEGATE.getRoleName(), dept, startOfToday);
189 		        		}
190 		        	} else {
191                         if (CollectionUtils.isNotEmpty(clockActionForm.getAssignmentDescriptions().entrySet())) {
192                             //only assignments that target user and logged in user have access to should be in this list
193                             isApproverOrReviewerForCurrentAssignment = true;
194                         }
195 
196                     }
197 		        	clockActionForm.setClockButtonEnabled(isApproverOrReviewerForCurrentAssignment);
198 		        }
199 		        
200 		        clockActionForm.setShowLunchButton(TkServiceLocator.getSystemLunchRuleService().isShowLunchButton());
201 		        assignShowDistributeButton(clockActionForm);
202 		        
203 		        if (clockActionForm.isShowLunchButton()) {
204 		            // We don't need to worry about the assignments and lunch rules
205 		            // if the global lunch rule is turned off.
206 		
207 		            // Check for presence of department lunch rule.
208 		            Map<String, Boolean> assignmentDeptLunchRuleMap = new HashMap<String, Boolean>();
209 		            for (Assignment a : timesheetDocument.getAssignments()) {
210 	                    String key = AssignmentDescriptionKey.getAssignmentKeyString(a);
211 	                    DeptLunchRule deptLunchRule = TkServiceLocator.getDepartmentLunchRuleService().getDepartmentLunchRule(a.getDept(), a.getWorkArea(), clockActionForm.getPrincipalId(), a.getJobNumber(), LocalDate.now());
212 	                    assignmentDeptLunchRuleMap.put(key, deptLunchRule != null);
213 	                }
214 		            clockActionForm.setAssignmentLunchMap(assignmentDeptLunchRuleMap);
215 		        }
216 	        } else {
217 	        	clockActionForm.setErrorMessage("Your current timesheet is already submitted for Approval. Clock action is not allowed on this timesheet.");
218 	        }
219         }
220         
221         return actionForward;
222     }
223     
224     public void assignShowDistributeButton(ClockActionForm caf) {
225     	caf.setShowDistrubuteButton(false);
226     	
227     	TimesheetDocument timesheetDocument = caf.getTimesheetDocument();
228         if (timesheetDocument != null) {
229             int eligibleAssignmentCount = 0;
230             for (Assignment a : timesheetDocument.getAssignments()) {
231                 WorkArea aWorkArea = HrServiceLocator.getWorkAreaService().getWorkArea(a.getWorkArea(), timesheetDocument.getDocEndDate());
232                 if(aWorkArea != null && aWorkArea.isHrsDistributionF()) {
233                     eligibleAssignmentCount++;
234                 }
235 
236                 // Only show the distribute button if there is more than one eligible assignment
237                 if (eligibleAssignmentCount > 1) {
238                     caf.setShowDistrubuteButton(true);
239                     break;
240                 }
241             }
242         }
243     }
244     
245     public ActionForward clockAction(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
246         ClockActionForm caf = (ClockActionForm) form;
247 
248         // TODO: Validate that clock action is valid for this user
249         // TODO: this needs to be integrated with the error tag
250         if (StringUtils.isBlank(caf.getSelectedAssignment())) {
251             caf.setErrorMessage("No assignment selected.");
252             return mapping.findForward("basic");
253         }
254         String pId = HrContext.getTargetPrincipalId();
255         ClockLog previousClockLog = TkServiceLocator.getClockLogService().getLastClockLog(pId);
256         if(previousClockLog != null && StringUtils.equals(caf.getCurrentClockAction(), previousClockLog.getClockAction())){
257         	caf.setErrorMessage("The operation is already performed.");
258             return mapping.findForward("basic");
259         }
260         String ip = TKUtils.getIPAddressFromRequest(request);
261         Assignment assignment = caf.getTimesheetDocument().getAssignment(AssignmentDescriptionKey.get(caf.getSelectedAssignment()));
262         
263         List<Assignment> lstAssingmentAsOfToday = HrServiceLocator.getAssignmentService().getAssignments(pId, LocalDate.now());
264         boolean foundValidAssignment = false;
265         for(Assignment assign : lstAssingmentAsOfToday){
266         	if((assign.getJobNumber().compareTo(assignment.getJobNumber()) ==0) &&
267         		(assign.getWorkArea().compareTo(assignment.getWorkArea()) == 0) &&
268         		(assign.getTask().compareTo(assignment.getTask()) == 0)){
269         		foundValidAssignment = true;
270         		break;
271         	}
272         }
273         
274         if(!foundValidAssignment){
275         	caf.setErrorMessage("Assignment is not effective as of today");
276         	return mapping.findForward("basic");
277         }
278         
279         LocalDate beginDate = LocalDate.now();
280    	 	DateTime clockBeginDateTime = new DateTime(beginDate.toDateTimeAtCurrentTime());
281         // validate if there's any overlapping with existing time blocks
282         if (StringUtils.equals(caf.getCurrentClockAction(), TkConstants.CLOCK_IN) || StringUtils.equals(caf.getCurrentClockAction(), TkConstants.LUNCH_IN)) {
283         	 List<TimeBlock> tbList = caf.getTimesheetDocument().getTimeBlocks();
284 	         for(TimeBlock tb : tbList) {
285 	        	 String earnCode = tb.getEarnCode();
286 	        	 boolean isRegularEarnCode = StringUtils.equals(assignment.getJob().getPayTypeObj().getRegEarnCode(),earnCode);
287 	        	 EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(earnCode, caf.getTimesheetDocument().getAsOfDate());
288 	        	 if(earnCodeObj != null && HrConstants.EARN_CODE_TIME.equals(earnCodeObj.getEarnCodeType())) {
289 	        		 Interval clockInterval = new Interval(new DateTime(tb.getBeginTimestamp().getTime()), new DateTime(tb.getEndTimestamp().getTime()));
290 	        		 if(isRegularEarnCode && clockInterval.contains(clockBeginDateTime.getMillis())) {
291 	        			 caf.setErrorMessage(TIME_BLOCK_OVERLAP_ERROR);
292 	        			 return mapping.findForward("basic");
293 	        		 }
294 	        	 }
295 	         }
296         }
297         
298         DateTime currentDateTime = new DateTime();
299         // for clock out and lunch out actions, check if the current time and last clock log time is on two different calendar entries,
300         // if they are, we need to clock out the employee at the endDatTime (in employee's time zone) of the last calendar entry,
301         // and clock employee back in at the beginDateTime (in employee's time zone) of the new calendar entry
302         // then clock him out again at current time. 
303         if (StringUtils.equals(caf.getCurrentClockAction(), TkConstants.CLOCK_OUT) || StringUtils.equals(caf.getCurrentClockAction(), TkConstants.LUNCH_OUT)) {
304             ClockLog lastLog = null;
305             String inAction = "";
306             String outAction = "";
307           	if (StringUtils.equals(caf.getCurrentClockAction(), TkConstants.LUNCH_OUT)) {
308                lastLog = TkServiceLocator.getClockLogService().getLastClockLog(pId, TkConstants.CLOCK_IN);
309                inAction = TkConstants.LUNCH_IN;
310                outAction = TkConstants.LUNCH_OUT;
311             } else if (StringUtils.equals(caf.getCurrentClockAction(), TkConstants.CLOCK_OUT)) {
312                lastLog = TkServiceLocator.getClockLogService().getLastClockLog(pId);
313                inAction = TkConstants.CLOCK_IN;
314                outAction = TkConstants.CLOCK_OUT;
315             }     
316         	
317         	TimesheetDocument previousTimeDoc = TkServiceLocator.getTimesheetService().getTimesheetDocument(previousClockLog.getDocumentId());
318         	if(previousTimeDoc != null) {
319 	        	CalendarEntry previousCalEntry = previousTimeDoc.getCalendarEntry();
320 	        	DateTime previousEndPeriodDateTime = previousCalEntry.getEndPeriodFullDateTime();
321 	        	// if current time is after the end time of previous calendar entry, it means the clock action covers two calendar entries
322 	        	if(currentDateTime.isAfter(previousEndPeriodDateTime.getMillis())) {
323 	        		
324 	        		// create co, ci and co clock logs and assign the last co clock log to the form
325 	        		// use the user's time zone and the system time zone to figure out the system time of endPeriodDatTime in the user's timezone
326 	                DateTimeZone userTimezone = DateTimeZone.forID(HrServiceLocator.getTimezoneService().getUserTimezone(pId));
327 	        		DateTimeZone systemTimeZone = TKUtils.getSystemDateTimeZone();
328 	        		// time to use to create the out clock log
329 	                DateTime outLogDateTime = TKUtils.convertTimeForDifferentTimeZone(previousEndPeriodDateTime, systemTimeZone, userTimezone);
330 	        	        
331 	                CalendarEntry nextCalendarEntry = HrServiceLocator.getCalendarEntryService().getNextCalendarEntryByCalendarId(previousCalEntry.getHrCalendarId(), previousCalEntry);
332 	                DateTime beginNextPeriodDateTime = nextCalendarEntry.getBeginPeriodFullDateTime();
333 	                // time to use to create the CI clock log
334 	                DateTime inLogDateTime = TKUtils.convertTimeForDifferentTimeZone(beginNextPeriodDateTime, systemTimeZone, userTimezone);
335 	                
336 	                TimesheetDocumentHeader nextTdh = TkServiceLocator.getTimesheetDocumentHeaderService()
337 	                		.getDocumentHeader(pId, nextCalendarEntry.getBeginPeriodFullDateTime(), nextCalendarEntry.getEndPeriodFullDateTime());
338 	                if(nextTdh == null) {
339 	                	 caf.setErrorMessage(DOCUMENT_NOT_INITIATE_ERROR);
340 		            	 return mapping.findForward("basic");
341 	                }
342 	                TimesheetDocument nextTimeDoc = TkServiceLocator.getTimesheetService().getTimesheetDocument(nextTdh.getDocumentId());
343 	                if(nextTimeDoc == null) {
344 	                	 caf.setErrorMessage(DOCUMENT_NOT_INITIATE_ERROR);
345 		            	 return mapping.findForward("basic");
346 	                }
347 	        	    // validate if there's any overlapping with existing time blocks
348 	        		if (lastLog != null) {
349 	        			// validation with previous calendar entry
350 	        			// the datetime for the new clock log that's about to be created with grace period rule applied
351 	        			DateTime endDateTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(outLogDateTime, previousCalEntry.getBeginPeriodFullDateTime().toLocalDate());
352 	        			boolean validation = this.validateOverlapping(previousTimeDoc.getAsOfDate(), previousTimeDoc.getTimeBlocks(), lastLog.getClockDateTime(), endDateTime,assignment);
353 	        			if(!validation) {
354 	        				 caf.setErrorMessage(TIME_BLOCK_OVERLAP_ERROR);
355    		            		 return mapping.findForward("basic");
356 	        			}
357 	        			
358 	        			// validation with the next calendar entry
359 		   	             // the datetime for the new clock log that's about to be created with grace period rule applied
360 	        			endDateTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(currentDateTime, nextCalendarEntry.getBeginPeriodFullDateTime().toLocalDate());
361 	        			validation = this.validateOverlapping(nextTimeDoc.getAsOfDate(), nextTimeDoc.getTimeBlocks(), inLogDateTime, endDateTime,assignment);
362 	        			if(!validation) {
363 	        				 caf.setErrorMessage(TIME_BLOCK_OVERLAP_ERROR);
364   		            		 return mapping.findForward("basic");
365 	        			}
366 		            } 
367 	                
368 	                // clock out employee at the end of the previous pay period
369 	                ClockLog outLog = TkServiceLocator.getClockLogService().processClockLog(outLogDateTime, assignment, previousCalEntry, ip,
370 	                		previousEndPeriodDateTime.toLocalDate(), previousTimeDoc, outAction, true, pId);
371 	                
372 	                // clock in employee at the begin of the next pay period
373 	                ClockLog inLog = TkServiceLocator.getClockLogService().processClockLog(inLogDateTime, assignment, nextCalendarEntry, ip,
374 	                		beginNextPeriodDateTime.toLocalDate(), nextTimeDoc, inAction, true, pId);
375 	                
376 	                // finally clock out employee at current time
377 	                ClockLog finalOutLog = TkServiceLocator.getClockLogService().processClockLog(currentDateTime, assignment, nextCalendarEntry, ip,
378 	                		currentDateTime.toLocalDate(), nextTimeDoc, caf.getCurrentClockAction(), true, pId);
379 	                
380 	                // add 5 seconds to clock out log's timestamp so it will be found as the latest clock action
381 	                Timestamp ts= finalOutLog.getTimestamp();
382 	                java.util.Calendar cal = java.util.Calendar.getInstance();
383 	                cal.setTimeInMillis(ts.getTime());
384 	                cal.add(java.util.Calendar.SECOND, 5);
385 	                Timestamp later = new Timestamp(cal.getTime().getTime());
386 	                finalOutLog.setTimestamp(later);
387 	                TkServiceLocator.getClockLogService().saveClockLog(finalOutLog);
388 	                
389 	                caf.setClockLog(finalOutLog);  
390 	                return mapping.findForward("basic");
391 	                
392 	            } else {	// covers the scenario that user clocks out on the same calendar entry
393 	                if (lastLog != null) {
394 		   	            // the datetime for the new clock log that's about to be created with grace period rule applied
395 		   	         	DateTime endDateTime = TkServiceLocator.getGracePeriodService().processGracePeriodRule(new DateTime(), caf.getCalendarEntry().getBeginPeriodFullDateTime().toLocalDate());
396 		   	         	
397 		   	         	boolean validation = this.validateOverlapping(caf.getTimesheetDocument().getAsOfDate(), caf.getTimesheetDocument().getTimeBlocks(), lastLog.getClockDateTime(), endDateTime,assignment);
398 	        			if(!validation) {
399 	        				 caf.setErrorMessage(TIME_BLOCK_OVERLAP_ERROR);
400 			            		 return mapping.findForward("basic");
401 	        			}
402 	                } 
403 	        	}
404     		}
405     	} 
406         // create clock log 
407         ClockLog clockLog = TkServiceLocator.getClockLogService().processClockLog(new DateTime(), assignment, caf.getCalendarEntry(), ip,
408         		beginDate, caf.getTimesheetDocument(), caf.getCurrentClockAction(), true, pId);
409 
410         caf.setClockLog(clockLog);  
411         return mapping.findForward("basic"); 
412         
413     }
414 
415     public boolean validateOverlapping(LocalDate asOfDate, List<TimeBlock> tbList, DateTime beginDateTime, DateTime endDateTime, Assignment assignment) {
416     	Interval clockInterval = new Interval(beginDateTime, endDateTime);
417     	if(clockInterval != null) {
418 	    	for(TimeBlock tb : tbList) {
419 	        	 String earnCode = tb.getEarnCode();
420 	        	 boolean isRegularEarnCode = StringUtils.equals(assignment.getJob().getPayTypeObj().getRegEarnCode(),earnCode);
421 	        	 EarnCode earnCodeObj = HrServiceLocator.getEarnCodeService().getEarnCode(earnCode, asOfDate);
422 	        	 if(isRegularEarnCode && earnCodeObj != null && HrConstants.EARN_CODE_TIME.equals(earnCodeObj.getEarnCodeType())) {
423 	            	 if(clockInterval.contains(tb.getBeginDateTime().getMillis()) || clockInterval.contains(tb.getEndDateTime().getMillis())) {
424 	            		 return false;
425 	            	 }
426 	        	 }
427 	    	}
428 	    	return true;
429     	}
430 		return false;
431     }
432     
433     
434     public ActionForward distributeTimeBlocks(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
435         ClockActionForm caf = (ClockActionForm) form;
436         caf.findTimeBlocksToDistribute();
437         return mapping.findForward("tb");
438     }
439 
440 
441     public ActionForward editTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
442         ClockActionForm caf = (ClockActionForm) form;
443         TimeBlock tb = caf.getCurrentTimeBlock();
444         caf.setCurrentAssignmentKey(tb.getAssignmentKey());
445         caf.populateAssignmentsForSelectedTimeBlock(tb);
446         ActionForward forward = mapping.findForward("et");
447 
448         return new ActionForward(forward.getPath() + "?editTimeBlockId=" + tb.getTkTimeBlockId().toString());
449 
450     }
451     public ActionForward addTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
452         ClockActionForm caf = (ClockActionForm) form;
453         TimeBlock currentTb = caf.getCurrentTimeBlock();
454         List<TimeBlock> newTimeBlocks = caf.getTimesheetDocument().getTimeBlocks();
455         List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(caf.getTimesheetDocument().getTimeBlocks().size());
456         for (TimeBlock tb : caf.getTimesheetDocument().getTimeBlocks()) {
457             referenceTimeBlocks.add(tb.copy());
458         }
459         //call persist method that only saves added/deleted/changed timeblocks
460         TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, HrContext.getPrincipalId());
461 
462         ActionForward forward = mapping.findForward("et");
463 
464         return new ActionForward(forward.getPath() + "?editTimeBlockId=" + currentTb.getTkTimeBlockId().toString());
465     }
466     
467     public ActionForward saveNewTimeBlocks(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){
468 		ClockActionForm caf = (ClockActionForm)form;
469 		String tbId = caf.getTbId();
470 		String timesheetDocId = caf.getTsDocId();
471 
472 		String[] assignments = caf.getNewAssignDesCol().split(SEPERATOR);
473 		String[] beginDates = caf.getNewBDCol().split(SEPERATOR);
474 		String[] beginTimes = caf.getNewBTCol().split(SEPERATOR);
475 		String[] endDates = caf.getNewEDCol().split(SEPERATOR);
476 		String[] endTimes = caf.getNewETCol().split(SEPERATOR);
477 		String[] hrs = caf.getNewHrsCol().split(SEPERATOR);
478 		String earnCode = TkServiceLocator.getTimeBlockService().getTimeBlock(tbId).getEarnCode();
479 		TimesheetDocument tsDoc = TkServiceLocator.getTimesheetService().getTimesheetDocument(timesheetDocId);
480 		List<TimeBlock> newTbList = new ArrayList<TimeBlock>();
481 		if(tsDoc != null) {
482 			for(TimeBlock oldTB : tsDoc.getTimeBlocks()) {
483 				if(!(oldTB.getTkTimeBlockId().compareTo(tbId) == 0)) {
484 					newTbList.add(oldTB);
485 				}
486 			}
487 		}
488 		for(int i = 0; i < hrs.length; i++) {
489 			BigDecimal hours = new BigDecimal(hrs[i]);
490 			DateTime beginDateTime = TKUtils.convertDateStringToDateTime(beginDates[i], beginTimes[i]);
491 			DateTime endDateTime = TKUtils.convertDateStringToDateTime(endDates[i], endTimes[i]);
492 			String assignString = assignments[i];
493 			Assignment assignment = HrServiceLocator.getAssignmentService().getAssignment(assignString);
494 			
495 			TimeBlock tb = TkServiceLocator.getTimeBlockService().createTimeBlock(tsDoc, beginDateTime, endDateTime, assignment, earnCode, hours,BigDecimal.ZERO, false, false, HrContext.getPrincipalId());
496 			newTbList.add(tb);
497 		}
498 		TkServiceLocator.getTimeBlockService().resetTimeHourDetail(newTbList);
499 		TkServiceLocator.getTkRuleControllerService().applyRules(TkConstants.ACTIONS.ADD_TIME_BLOCK, newTbList, new ArrayList<LeaveBlock>(), tsDoc.getCalendarEntry(), tsDoc, tsDoc.getPrincipalId());
500 		TkServiceLocator.getTimeBlockService().saveTimeBlocks(newTbList);
501 		TimeBlock oldTB = TkServiceLocator.getTimeBlockService().getTimeBlock(tbId);
502 		TkServiceLocator.getTimeBlockService().deleteTimeBlock(oldTB);
503 		return mapping.findForward("basic");
504 	}
505 	
506 	public ActionForward validateNewTimeBlock(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response){
507 		ClockActionForm caf = (ClockActionForm)form;
508 		String tbId = caf.getTbId();
509 		String[] assignments = caf.getNewAssignDesCol().split(SEPERATOR);
510 		String[] beginDates = caf.getNewBDCol().split(SEPERATOR);
511 		String[] beginTimes = caf.getNewBTCol().split(SEPERATOR);
512 		String[] endDates = caf.getNewEDCol().split(SEPERATOR);
513 		String[] endTimes = caf.getNewETCol().split(SEPERATOR);
514 		String[] hrs = caf.getNewHrsCol().split(SEPERATOR);
515 
516 		List<Interval> newIntervals = new ArrayList<Interval>();
517 		JSONArray errorMsgList = new JSONArray();
518 
519 		// validates that all fields are available
520 		if(assignments.length != beginDates.length ||
521 				assignments.length!= beginTimes.length ||
522 				assignments.length != endDates.length ||
523 				assignments.length != endTimes.length ||
524 				assignments.length != hrs.length) {
525 			errorMsgList.add("All fields are required");
526 		    caf.setOutputString(JSONValue.toJSONString(errorMsgList));
527 		    return mapping.findForward("ws");
528 		}
529 
530 		for(int i = 0; i < hrs.length; i++) {
531 			String index = String.valueOf(i+1);
532 
533 			// validate the hours field
534 			BigDecimal dc = new BigDecimal(hrs[i]);
535 		    if (dc.compareTo(new BigDecimal("0")) == 0) {
536 		        errorMsgList.add("The entered hours for entry " + index + " is not valid.");
537 		        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
538 		        return mapping.findForward("ws");
539 		    }
540 
541 		    // check if the begin / end time are valid
542 		    // should not include time zone in consideration when conparing time intervals
543 		    DateTime beginDateTime = TKUtils.convertDateStringToDateTimeWithoutZone(beginDates[i], beginTimes[i]);
544 			DateTime endDateTime = TKUtils.convertDateStringToDateTimeWithoutZone(endDates[i], endTimes[i]);
545 		    if ((beginDateTime.compareTo(endDateTime) > 0 || endDateTime.compareTo(beginDateTime) < 0)) {
546 		        errorMsgList.add("The time or date for entry " + index + " is not valid.");
547 		        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
548 		        return mapping.findForward("ws");
549 		    }
550 
551 		    // check if new time blocks overlap with existing time blocks
552 		    Interval addedTimeblockInterval = new Interval(beginDateTime, endDateTime);
553 		    newIntervals.add(addedTimeblockInterval);
554 		    for (TimeBlock timeBlock : caf.getTimesheetDocument().getTimeBlocks()) {
555 		    	if(timeBlock.getTkTimeBlockId().equals(tbId)) {	// ignore the original time block
556 		    		continue;
557 		    	}
558 		    	if(timeBlock.getHours().compareTo(BigDecimal.ZERO) == 0) { // ignore time blocks with zero hours
559 		    		continue;
560 		    	}
561 		    	DateTimeZone dateTimeZone = HrServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
562 		    	DateTime timeBlockBeginTimestamp = new DateTime(timeBlock.getBeginTimestamp().getTime(), dateTimeZone).withZone(TKUtils.getSystemDateTimeZone());
563 		    	DateTime timeBlockEndTimestamp = new DateTime(timeBlock.getEndTimestamp().getTime(), dateTimeZone).withZone(TKUtils.getSystemDateTimeZone());
564 		    	Interval timeBlockInterval = new Interval(timeBlockBeginTimestamp, timeBlockEndTimestamp);
565 			    if (timeBlockInterval.overlaps(addedTimeblockInterval)) {
566 			        errorMsgList.add("The time block you are trying to add for entry " + index + " overlaps with an existing time block.");
567 			        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
568 			        return mapping.findForward("ws");
569 			    }
570 		    }
571 		}
572 		// check if new time blocks overlap with each other
573 		if(newIntervals.size() > 1 ) {
574 			for(Interval intv1 : newIntervals) {
575 				for(Interval intv2 : newIntervals) {
576 					if(intv1.equals(intv2)) {
577 						continue;
578 					}
579 					if (intv1.overlaps(intv2)) {
580 						errorMsgList.add("There is time overlap between the entries.");
581 				        caf.setOutputString(JSONValue.toJSONString(errorMsgList));
582 				        return mapping.findForward("ws");
583 					}
584 				}
585 			}
586 		}
587 
588 	    caf.setOutputString(JSONValue.toJSONString(errorMsgList));
589 		return mapping.findForward("ws");
590  	}
591 	
592 	 private Boolean isPrincipalAnyProcessorInWorkArea(String principalId, Long tbWorkArea, LocalDate asOfDate) {
593 	    	Boolean flag = false;
594 	        Set<Long> workAreas = new HashSet<Long>();
595 	    	workAreas.addAll(HrServiceLocator.getKPMERoleService().getWorkAreasForPrincipalInRole(principalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR.getRoleName(), LocalDate.now().toDateTimeAtStartOfDay(), true));
596 	        workAreas.addAll(HrServiceLocator.getKPMERoleService().getWorkAreasForPrincipalInRole(principalId, KPMENamespace.KPME_HR.getNamespaceCode(),  KPMERole.PAYROLL_PROCESSOR_DELEGATE.getRoleName(), LocalDate.now().toDateTimeAtStartOfDay(), true));
597 	        for (Long wa : workAreas) {
598 	            WorkArea workArea = HrServiceLocator.getWorkAreaService().getWorkArea(wa, asOfDate);
599 	            if (workArea!= null && tbWorkArea.compareTo(wa)==0) {
600 	                flag = true;
601 	                break;
602 	            }
603 	        }
604 	        return flag;
605 	    }
606     
607 }