001 /**
002 * Copyright 2004-2013 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.hr.time.clock.web;
017
018 import java.math.BigDecimal;
019 import java.sql.Timestamp;
020 import java.text.SimpleDateFormat;
021 import java.util.ArrayList;
022 import java.util.Date;
023 import java.util.HashMap;
024 import java.util.List;
025 import java.util.Map;
026
027 import javax.servlet.http.HttpServletRequest;
028 import javax.servlet.http.HttpServletResponse;
029
030 import org.apache.commons.lang.StringUtils;
031 import org.apache.log4j.Logger;
032 import org.apache.struts.action.ActionForm;
033 import org.apache.struts.action.ActionForward;
034 import org.apache.struts.action.ActionMapping;
035 import org.joda.time.DateTime;
036 import org.joda.time.DateTimeZone;
037 import org.joda.time.Interval;
038 import org.json.simple.JSONArray;
039 import org.json.simple.JSONValue;
040 import org.kuali.hr.time.assignment.Assignment;
041 import org.kuali.hr.time.assignment.AssignmentDescriptionKey;
042 import org.kuali.hr.time.clocklog.ClockLog;
043 import org.kuali.hr.time.collection.rule.TimeCollectionRule;
044 import org.kuali.hr.time.roles.TkUserRoles;
045 import org.kuali.hr.time.roles.UserRoles;
046 import org.kuali.hr.time.service.base.TkServiceLocator;
047 import org.kuali.hr.time.timeblock.TimeBlock;
048 import org.kuali.hr.time.timesheet.TimesheetDocument;
049 import org.kuali.hr.time.timesheet.web.TimesheetAction;
050 import org.kuali.hr.time.timesheet.web.TimesheetActionForm;
051 import org.kuali.hr.time.util.TKContext;
052 import org.kuali.hr.time.util.TKUser;
053 import org.kuali.hr.time.util.TKUtils;
054 import org.kuali.hr.time.util.TkConstants;
055 import org.kuali.rice.krad.exception.AuthorizationException;
056 import org.kuali.rice.krad.util.GlobalVariables;
057
058 public class ClockAction extends TimesheetAction {
059
060 private static final Logger LOG = Logger.getLogger(ClockAction.class);
061 public static final SimpleDateFormat SDF = new SimpleDateFormat("EEE, MMMM d yyyy HH:mm:ss, zzzz");
062 public static final String SEPERATOR = "[****]+";
063
064 @Override
065 protected void checkTKAuthorization(ActionForm form, String methodToCall) throws AuthorizationException {
066 super.checkTKAuthorization(form, methodToCall); // Checks for read access first.
067
068 TimesheetActionForm taForm = (TimesheetActionForm) form;
069 UserRoles roles = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId());
070 TimesheetDocument doc = TKContext.getCurrentTimesheetDocument();
071
072 // Check for write access to Timeblock.
073 if (StringUtils.equals(methodToCall, "clockAction") ||
074 StringUtils.equals(methodToCall, "addTimeBlock") ||
075 StringUtils.equals(methodToCall, "editTimeBlock") ||
076 StringUtils.equals(methodToCall, "distributeTimeBlocks") ||
077 StringUtils.equals(methodToCall, "saveNewTimeBlocks") ||
078 StringUtils.equals(methodToCall, "deleteTimeBlock")) {
079 if (!roles.isDocumentWritable(doc)) {
080 throw new AuthorizationException(roles.getPrincipalId(), "ClockAction", "");
081 }
082 }
083 }
084
085
086 @Override
087 public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
088 ActionForward forward = super.execute(mapping, form, request, response);
089 ClockActionForm caf = (ClockActionForm) form;
090 caf.setCurrentServerTime(String.valueOf(new Date().getTime()));
091 caf.getUserSystemOffsetServerTime();
092 caf.setShowLunchButton(TkServiceLocator.getSystemLunchRuleService().isShowLunchButton());
093 caf.setAssignmentDescriptions(TkServiceLocator.getAssignmentService().getAssignmentDescriptions(caf.getTimesheetDocument(), true));
094 if (caf.isShowLunchButton()) {
095 // We don't need to worry about the assignments and lunch rules
096 // if the global lunch rule is turned off.
097
098 // Check for presence of department lunch rule.
099 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 }