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.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 067 return valid; 068 } 069 070 /** 071 * Checks whether the provided MissedPunch has a valid time based on the 072 * previous ClockLog. 073 * 074 * @param mp The MissedPunch we are validating. 075 * @param lastClock The ClockLog entry that happened before the one we want to create. 076 * 077 * @return true if valid, false otherwise. 078 * @throws ParseException 079 */ 080 boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException { 081 boolean valid = true; 082 083 if (lastClock == null) 084 return valid; 085 086 //Missed Action Date and Missed Action Time are required fields. KPME-1853 087 if(mp.getActionTime() == null || mp.getActionDate() == null) 088 return false; 089 090 DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime()); 091 DateTime boundaryMax = clockLogDateTime.plusDays(1); 092 DateTime nowTime = new DateTime(TKUtils.getCurrentDate()); 093 094 SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); 095 String s = formatter.format(mp.getActionDate()); 096 Date tempDate = formatter.parse(s); 097 Timestamp dateLocal = new Timestamp(tempDate.getTime()); 098 LocalTime timeLocal = new LocalTime(mp.getActionTime().getTime()); 099 DateTime actionDateTime = new DateTime(dateLocal.getTime()); 100 actionDateTime = actionDateTime.plus(timeLocal.getMillisOfDay()); 101 102 // convert the action time to the system zone 103 Timestamp ts = new Timestamp(actionDateTime.getMillis()); 104 ClockLog lastLog = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId()); 105 Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastLog.getClockTimestampTimezone())); 106 Timestamp actionTime = new Timestamp(ts.getTime()-zoneOffset); 107 DateTime newDateTime = new DateTime(actionTime.getTime()); 108 109 // if date is a future date 110 if(actionDateTime.getYear()> nowTime.getYear() 111 || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) { 112 GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date"); 113 return false; 114 } 115 116 // if time is a future time 117 if(actionDateTime.getMillis() > nowTime.getMillis()) { 118 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time"); 119 return false; 120 } 121 122 if ( ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax)) 123 || newDateTime.isBefore(clockLogDateTime)) && StringUtils.equals(mp.getDocumentStatus(),"R")) { 124 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime"); 125 valid = false; 126 } 127 128 return valid; 129 } 130 131 // do not allow a missed punch if the time sheet document is enroute or final 132 boolean validateTimeSheet(MissedPunchDocument mp) { 133 boolean valid = true; 134 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId()); 135 if(tsd != null 136 && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 137 || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) { 138 GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet"); 139 valid = false; 140 } 141 142 return valid; 143 } 144 @Override 145 public boolean processRouteDocument(Document document) { 146 boolean ret = super.processRouteDocument(document); 147 MissedPunchDocument mpDoc = (MissedPunchDocument)document; 148 if(!validateTimeSheet(mpDoc)) { 149 return false; 150 } 151 ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(mpDoc.getPrincipalId()); 152 ret &= validateClockAction(mpDoc, lastClock); 153 try { 154 ret &= validateClockTime(mpDoc, lastClock); 155 } catch (ParseException e) { 156 // TODO Auto-generated catch block 157 e.printStackTrace(); 158 } 159 160 return ret; 161 } 162 }