001 /** 002 * Copyright 2004-2012 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.missedpunch; 017 018 import java.sql.Timestamp; 019 import java.text.ParseException; 020 import java.text.SimpleDateFormat; 021 import java.util.Date; 022 import java.util.HashSet; 023 import java.util.Set; 024 025 import org.apache.commons.lang.StringUtils; 026 import org.joda.time.DateTime; 027 import org.joda.time.DateTimeZone; 028 import org.joda.time.LocalTime; 029 import org.kuali.hr.time.clocklog.ClockLog; 030 import org.kuali.hr.time.service.base.TkServiceLocator; 031 import org.kuali.hr.time.timesheet.TimesheetDocument; 032 import org.kuali.hr.time.util.TKUtils; 033 import org.kuali.hr.time.util.TkConstants; 034 import org.kuali.rice.krad.document.Document; 035 import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase; 036 import org.kuali.rice.krad.util.GlobalVariables; 037 038 public class MissedPunchValidation extends TransactionalDocumentRuleBase { 039 040 /** 041 * Checks the provided MissedPunch for a valid ClockAction. 042 * 043 * @param mp The MissedPunch we are validating. 044 * @param lastClock The ClockLog entry that happened before the one we want to create. 045 * 046 * @return true if valid, false otherwise. 047 */ 048 boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) { 049 boolean valid = true; 050 Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>(); 051 052 // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut 053 // missed punch can only be used on a tiemblock once. 054 if(lastClock != null 055 && (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) { 056 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId()); 057 if(mpd != null) { 058 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action"); 059 return false; 060 } 061 } 062 if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) { 063 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action"); 064 valid = false; 065 } 066 return valid; 067 } 068 069 /** 070 * Checks whether the provided MissedPunch has a valid time based on the 071 * previous ClockLog. 072 * 073 * @param mp The MissedPunch we are validating. 074 * @param lastClock The ClockLog entry that happened before the one we want to create. 075 * 076 * @return true if valid, false otherwise. 077 * @throws ParseException 078 */ 079 boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException { 080 boolean valid = true; 081 082 if (lastClock == null) 083 return valid; 084 085 DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime()); 086 DateTime boundaryMax = clockLogDateTime.plusDays(1); 087 DateTime nowTime = new DateTime(TKUtils.getCurrentDate()); 088 089 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); 090 String s = formatter.format(mp.getActionDate()); 091 Date tempDate = formatter.parse(s); 092 Timestamp dateLocal = new Timestamp(tempDate.getTime()); 093 LocalTime timeLocal = new LocalTime(mp.getActionTime().getTime()); 094 DateTime actionDateTime = new DateTime(dateLocal.getTime()); 095 actionDateTime = actionDateTime.plus(timeLocal.getMillisOfDay()); 096 097 // convert the action time to the system zone 098 Timestamp ts = new Timestamp(actionDateTime.getMillis()); 099 ClockLog lastLog = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId()); 100 Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastLog.getClockTimestampTimezone())); 101 Timestamp actionTime = new Timestamp(ts.getTime()-zoneOffset); 102 DateTime newDateTime = new DateTime(actionTime.getTime()); 103 104 // if date is a future date 105 if(actionDateTime.getYear()> nowTime.getYear() 106 || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) { 107 GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date"); 108 return false; 109 } 110 111 // if time is a future time 112 if(actionDateTime.getMillis() > nowTime.getMillis()) { 113 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time"); 114 return false; 115 } 116 117 118 if ( ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax)) 119 || newDateTime.isBefore(clockLogDateTime)) && StringUtils.equals(mp.getDocumentStatus(),"R")) { 120 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime"); 121 valid = false; 122 } 123 124 return valid; 125 } 126 127 // do not allow a missed punch is the time sheet document is enroute or final 128 boolean validateTimeSheet(MissedPunchDocument mp) { 129 boolean valid = true; 130 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId()); 131 if(tsd != null 132 && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 133 || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) { 134 GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet"); 135 valid = false; 136 } 137 return valid; 138 } 139 @Override 140 public boolean processRouteDocument(Document document) { 141 boolean ret = super.processRouteDocument(document); 142 MissedPunchDocument mpDoc = (MissedPunchDocument)document; 143 if(!validateTimeSheet(mpDoc)) { 144 return false; 145 } 146 ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(mpDoc.getPrincipalId()); 147 ret &= validateClockAction(mpDoc, lastClock); 148 try { 149 ret &= validateClockTime(mpDoc, lastClock); 150 } catch (ParseException e) { 151 // TODO Auto-generated catch block 152 e.printStackTrace(); 153 } 154 155 return ret; 156 } 157 }