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 org.apache.commons.lang.StringUtils; 019 import org.joda.time.DateTime; 020 import org.joda.time.LocalTime; 021 import org.kuali.hr.time.assignment.Assignment; 022 import org.kuali.hr.time.assignment.AssignmentDescriptionKey; 023 import org.kuali.hr.time.clocklog.ClockLog; 024 import org.kuali.hr.time.service.base.TkServiceLocator; 025 import org.kuali.hr.time.timesheet.TimesheetDocument; 026 import org.kuali.hr.time.util.TKUtils; 027 import org.kuali.hr.time.util.TkConstants; 028 import org.kuali.rice.kew.api.KewApiServiceLocator; 029 import org.kuali.rice.kew.api.document.DocumentStatus; 030 import org.kuali.rice.krad.document.Document; 031 import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase; 032 import org.kuali.rice.krad.util.GlobalVariables; 033 034 import java.text.ParseException; 035 import java.util.HashSet; 036 import java.util.Set; 037 038 public class MissedPunchValidation extends TransactionalDocumentRuleBase { 039 040 @Override 041 protected boolean processCustomRouteDocumentBusinessRules(Document document) { 042 boolean valid = true; 043 044 MissedPunchDocument missedPunchDocument = (MissedPunchDocument) document; 045 DocumentStatus documentStatus = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(missedPunchDocument.getDocumentNumber()); 046 047 if (DocumentStatus.INITIATED.equals(DocumentStatus.fromCode(documentStatus.getCode())) 048 || DocumentStatus.SAVED.equals(DocumentStatus.fromCode(documentStatus.getCode()))) { 049 valid &= validateTimeSheet(missedPunchDocument); 050 valid &= validateAssignment(missedPunchDocument); 051 052 if (valid) { 053 ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(missedPunchDocument.getPrincipalId()); 054 try { 055 valid &= validateClockAction(missedPunchDocument, lastClock); 056 valid &= validateClockTime(missedPunchDocument, lastClock); 057 } catch (ParseException e) { 058 e.printStackTrace(); 059 } 060 } 061 } 062 063 return valid; 064 } 065 066 // do not allow a missed punch if the time sheet document is enroute or final 067 boolean validateTimeSheet(MissedPunchDocument mp) { 068 boolean valid = true; 069 TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId()); 070 if(tsd != null 071 && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 072 || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) { 073 GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet"); 074 valid = false; 075 } 076 077 return valid; 078 } 079 080 /** 081 * Checks the provided MissedPunch for a valid ClockAction. 082 * 083 * @param mp The MissedPunch we are validating. 084 * @param lastClock The ClockLog entry that happened before the one we want to create. 085 * 086 * @return true if valid, false otherwise. 087 */ 088 boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) { 089 boolean valid = true; 090 Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>(); 091 092 093 if (mp.getClockAction().equals(TkConstants.CLOCK_OUT) || mp.getClockAction().equals(TkConstants.LUNCH_OUT)) { 094 ClockLog lci = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.CLOCK_IN); //last clock in 095 ClockLog lli = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId(),TkConstants.LUNCH_IN); //last lunch in 096 if (lci != null) { 097 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lci.getTkClockLogId()); 098 if(mpd != null) { 099 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action"); 100 return false; 101 } 102 } else if(lli != null) { 103 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lli.getTkClockLogId()); 104 if(mpd != null) { 105 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action"); 106 return false; 107 } 108 } 109 } 110 111 // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut 112 // missed punch can only be used on a tiemblock once. 113 // if(lastClock != null 114 // && (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) { 115 // MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId()); 116 // if(mpd != null) { 117 // GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action"); 118 // return false; 119 // } 120 // } 121 if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) { 122 GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action"); 123 valid = false; 124 } 125 126 return valid; 127 } 128 129 /** 130 * Checks whether the chosen assignment is valid (both chosen and within proper timeframe). 131 * Checks whether the provided MissedPunch has a valid time based on the 132 * previous ClockLog. 133 * 134 * @param mp The MissedPunch we are validating. 135 * @param lastClock The ClockLog entry that happened before the one we want to create. 136 * 137 * @return true if valid, false otherwise. 138 * @throws ParseException 139 */ 140 boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException { 141 boolean valid = true; 142 143 if (lastClock == null) { 144 return valid; 145 } 146 147 //Missed Action Date and Missed Action Time are required fields. KPME-1853 148 if(mp.getActionTime() == null || mp.getActionDate() == null) 149 return false; 150 151 DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime()); 152 DateTime boundaryMax = clockLogDateTime.plusDays(1); 153 DateTime nowTime = new DateTime(TKUtils.getCurrentDate()); 154 long offset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback()); 155 long dateTimeLocal = new LocalTime(mp.getActionTime()).getMillisOfDay() + mp.getActionDate().getTime() - offset; 156 157 //this will be in system's timezone, but offset with user's timezone 158 DateTime actionDateTime = new DateTime(dateTimeLocal); 159 160 // if date is a future date 161 if(actionDateTime.getYear()> nowTime.getYear() 162 || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) { 163 GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date"); 164 return false; 165 } 166 167 // if time is a future time 168 if(actionDateTime.getMillis() > nowTime.getMillis()) { 169 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time"); 170 return false; 171 } 172 173 if ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax)) 174 || actionDateTime.isBefore(clockLogDateTime)) { 175 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime"); 176 valid = false; 177 } 178 179 return valid; 180 } 181 182 /** 183 * Checks whether the chosen assignment is valid (both chosen and within proper timeframe). 184 * 185 * @param mp The MissedPunch we are validating. 186 * 187 * @return true if valid, false otherwise. 188 * @throws ParseException 189 */ 190 boolean validateAssignment(MissedPunchDocument mp) { 191 boolean valid = true; 192 193 String assignment = mp.getAssignment(); 194 195 if (mp.getAssignment() == null) { 196 GlobalVariables.getMessageMap().putError("document.assignment", "clock.mp.assignment.required"); 197 valid = false; 198 } 199 200 if (valid) 201 { 202 AssignmentDescriptionKey key = new AssignmentDescriptionKey(assignment); 203 if (key != null) { 204 Assignment assignmentObj = TkServiceLocator.getAssignmentService().getAssignment(mp.getPrincipalId(), key, new java.sql.Date(mp.getActionDate().getTime())); 205 if (assignmentObj == null) { 206 GlobalVariables.getMessageMap().putError("document.assignment", "clock.mp.assignment.active"); 207 valid = false; 208 } 209 } 210 else { 211 valid = false; 212 } 213 } 214 215 return valid; 216 } 217 }