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.service; 017 018 import java.math.BigDecimal; 019 import java.sql.Timestamp; 020 import java.util.ArrayList; 021 import java.util.List; 022 023 import org.apache.commons.lang.StringUtils; 024 import org.apache.log4j.Logger; 025 import org.joda.time.DateTime; 026 import org.joda.time.DateTimeZone; 027 import org.joda.time.LocalTime; 028 import org.kuali.hr.lm.leaveblock.LeaveBlock; 029 import org.kuali.hr.time.assignment.Assignment; 030 import org.kuali.hr.time.calendar.CalendarEntries; 031 import org.kuali.hr.time.clocklog.ClockLog; 032 import org.kuali.hr.time.missedpunch.MissedPunchDocument; 033 import org.kuali.hr.time.missedpunch.dao.MissedPunchDao; 034 import org.kuali.hr.time.service.base.TkServiceLocator; 035 import org.kuali.hr.time.timeblock.TimeBlock; 036 import org.kuali.hr.time.timesheet.TimesheetDocument; 037 import org.kuali.hr.time.util.TKContext; 038 import org.kuali.hr.time.util.TKUtils; 039 import org.kuali.hr.time.util.TkConstants; 040 import org.kuali.rice.core.api.config.property.ConfigContext; 041 import org.kuali.rice.kew.api.WorkflowDocument; 042 import org.kuali.rice.kew.api.WorkflowDocumentFactory; 043 import org.kuali.rice.kim.api.identity.principal.Principal; 044 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 045 import org.kuali.rice.krad.service.KRADServiceLocator; 046 import org.kuali.rice.krad.util.GlobalVariables; 047 048 public class MissedPunchServiceImpl implements MissedPunchService { 049 050 private static final Logger LOG = Logger.getLogger(MissedPunchServiceImpl.class); 051 052 MissedPunchDao missedPunchDao; 053 054 @Override 055 public MissedPunchDocument getMissedPunchByRouteHeader(String headerId) { 056 return missedPunchDao.getMissedPunchByRouteHeader(headerId); 057 } 058 059 public void setMissedPunchDao(MissedPunchDao missedPunchDao) { 060 this.missedPunchDao = missedPunchDao; 061 } 062 063 @Override 064 public void updateClockLogAndTimeBlockIfNecessary(MissedPunchDocument missedPunch) { 065 java.util.Date actionDate = missedPunch.getActionDate(); 066 java.sql.Time actionTime = missedPunch.getActionTime(); 067 068 LocalTime actionTimeLocal = new LocalTime(actionTime.getTime()); 069 DateTime actionDateTime = new DateTime(actionDate.getTime()); 070 071 actionDateTime = actionDateTime.plus(actionTimeLocal.getMillisOfDay()); 072 073 ClockLog cl = TkServiceLocator.getClockLogService().getClockLog(missedPunch.getTkClockLogId()); 074 // in case the missedpunch doc has an valication error but the clockLog has been changed at certain time 075 // need to reset the clock log back to the original one 076 if(cl == null) { 077 MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByRouteHeader(missedPunch.getDocumentNumber()); 078 if(mpd != null) { 079 missedPunch.setTkClockLogId(mpd.getTkClockLogId()); 080 cl = TkServiceLocator.getClockLogService().getClockLog(missedPunch.getTkClockLogId()); 081 } 082 } 083 084 if(cl != null && cl.getClockTimestamp().compareTo(new Timestamp(actionDateTime.getMillis())) != 0){ 085 //change occurred between the initial save and the approver 086 //inactivate all the previous timeblocks and delete clock logs 087 String logEndId = null; 088 String logBeginId = null; 089 List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocksForClockLogEndId(cl.getTkClockLogId()); 090 if(timeBlocks.isEmpty()) { 091 // get timeBlock with the Clock Log as the clock_log_begin_id 092 timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocksForClockLogBeginId(cl.getTkClockLogId()); 093 if(!timeBlocks.isEmpty()) { 094 logEndId = timeBlocks.get(0).getClockLogEndId(); 095 } 096 } else { 097 logBeginId = timeBlocks.get(0).getClockLogBeginId(); // new time blocks should keep the original clockLogBeginId 098 } 099 100 //delete existing time blocks 101 for(TimeBlock tb : timeBlocks){ 102 TkServiceLocator.getTimeBlockService().deleteTimeBlock(tb); 103 } 104 KRADServiceLocator.getBusinessObjectService().delete(cl); 105 // delete the existing clock log and add new time blocks 106 addClockLogForMissedPunch(missedPunch, logEndId, logBeginId); 107 } 108 } 109 110 @Override 111 public void addClockLogForMissedPunch(MissedPunchDocument missedPunch) { 112 java.util.Date actionDate = missedPunch.getActionDate(); 113 java.sql.Time actionTime = missedPunch.getActionTime(); 114 115 LocalTime actionTimeLocal = new LocalTime(actionTime.getTime()); 116 DateTime actionDateTime = new DateTime(actionDate.getTime()); 117 118 actionDateTime = actionDateTime.plus(actionTimeLocal.getMillisOfDay()); 119 missedPunch.setActionDate(new java.util.Date(actionDateTime.getMillis())); 120 TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().getTimesheetDocument(missedPunch.getTimesheetDocumentId()); 121 Assignment assign = TkServiceLocator.getAssignmentService().getAssignment(tdoc, missedPunch.getAssignment()); 122 // Need to build a clock log entry. 123 //Timestamp clockTimestamp, String selectedAssign, TimesheetDocument timesheetDocument, String clockAction, String ip) { 124 Timestamp ts = new Timestamp(missedPunch.getActionDate().getTime()); 125 ClockLog lastClockLog = TkServiceLocator.getClockLogService().getLastClockLog(missedPunch.getPrincipalId()); 126 Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastClockLog.getClockTimestampTimezone())); 127 Timestamp clockLogTime = new Timestamp(ts.getTime() - zoneOffset); // convert the action time to the system zone time 128 CalendarEntries calendarEntry = tdoc.getCalendarEntry(); 129 String principalId = tdoc.getPrincipalId(); 130 131 ClockLog clockLog = TkServiceLocator.getClockLogService().processClockLog(clockLogTime, assign, calendarEntry, 132 TKUtils.getIPAddressFromRequest(TKContext.getHttpServletRequest()), TKUtils.getCurrentDate(), tdoc, missedPunch.getClockAction(), false, principalId); 133 134 clockLog = TkServiceLocator.getClockLogService().saveClockLog(clockLog); 135 missedPunch.setActionDate(new java.sql.Date(clockLog.getClockTimestamp().getTime())); 136 missedPunch.setActionTime(new java.sql.Time(clockLog.getClockTimestamp().getTime())); 137 missedPunch.setTkClockLogId(clockLog.getTkClockLogId()); 138 139 if (StringUtils.equals(clockLog.getClockAction(), TkConstants.CLOCK_OUT) || 140 StringUtils.equals(clockLog.getClockAction(), TkConstants.LUNCH_OUT)) { 141 String earnCode = assign.getJob().getPayTypeObj().getRegEarnCode(); 142 this.buildTimeBlockRunRules(lastClockLog, clockLog, tdoc, assign, earnCode, lastClockLog.getClockTimestamp(), clockLog.getClockTimestamp()); 143 } 144 } 145 146 @Override 147 // is called by updateClockLogAndTimeBlockIfNecessary when approver changes time on approving an existing missed punch doc 148 149 public void addClockLogForMissedPunch(MissedPunchDocument missedPunch, String logEndId, String logBeginId) { 150 java.util.Date actionDate = missedPunch.getActionDate(); 151 java.sql.Time actionTime = missedPunch.getActionTime(); 152 153 LocalTime actionTimeLocal = new LocalTime(actionTime.getTime()); 154 DateTime actionDateTime = new DateTime(actionDate.getTime()); 155 156 actionDateTime = actionDateTime.plus(actionTimeLocal.getMillisOfDay()); 157 missedPunch.setActionDate(new java.util.Date(actionDateTime.getMillis())); 158 TimesheetDocument tdoc = TkServiceLocator.getTimesheetService().getTimesheetDocument(missedPunch.getTimesheetDocumentId()); 159 Assignment assign = TkServiceLocator.getAssignmentService().getAssignment(tdoc, missedPunch.getAssignment()); 160 // Need to build a clock log entry. 161 Timestamp ts = new Timestamp(missedPunch.getActionDate().getTime()); 162 ClockLog lastLog = TkServiceLocator.getClockLogService().getLastClockLog(missedPunch.getPrincipalId()); 163 Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastLog.getClockTimestampTimezone())); 164 Timestamp clockLogTime = new Timestamp(ts.getTime() - zoneOffset); // convert the action time to the system zone time 165 CalendarEntries calendarEntry = tdoc.getCalendarEntry(); 166 String principalId = tdoc.getPrincipalId(); 167 168 ClockLog clockLog = TkServiceLocator.getClockLogService().processClockLog(clockLogTime, assign, calendarEntry, 169 TKUtils.getIPAddressFromRequest(TKContext.getHttpServletRequest()), TKUtils.getCurrentDate(), tdoc, missedPunch.getClockAction(), false, principalId); 170 171 TkServiceLocator.getClockLogService().saveClockLog(clockLog); 172 missedPunch.setActionDate(new java.sql.Date(clockLog.getClockTimestamp().getTime())); 173 missedPunch.setActionTime(new java.sql.Time(clockLog.getClockTimestamp().getTime())); 174 missedPunch.setTkClockLogId(clockLog.getTkClockLogId()); 175 176 // MissedPunchDocument doc = TkServiceLocator.getMissedPunchService().getMissedPunchByRouteHeader(missedPunch.getDocumentNumber()); 177 // doc.setTkClockLogId(clockLog.getTkClockLogId()); 178 // KNSServiceLocator.getBusinessObjectService().save(doc); 179 180 // if both clock log ids are null, no need to create new time blocks 181 if(!(logEndId == null && logBeginId == null)) { 182 ClockLog endLog = null; 183 ClockLog beginLog = null; 184 if(logEndId != null) { 185 endLog = TkServiceLocator.getClockLogService().getClockLog(logEndId); 186 } else { 187 endLog = clockLog; 188 } 189 if (logBeginId != null) { 190 beginLog = TkServiceLocator.getClockLogService().getClockLog(logBeginId); 191 } else { 192 beginLog = clockLog; 193 } 194 195 if (beginLog != null && endLog != null && beginLog.getClockTimestamp().before(endLog.getClockTimestamp())) { 196 String earnCode = assign.getJob().getPayTypeObj().getRegEarnCode(); 197 this.buildTimeBlockRunRules(beginLog, endLog, tdoc, assign, earnCode, beginLog.getClockTimestamp(), endLog.getClockTimestamp()); 198 } else { 199 // error 200 GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime"); 201 } 202 } 203 } 204 205 /** 206 * Helper method to build time blocks and fire the rules processing. This 207 * should be called only if there was a CLOCK_OUT action. 208 */ 209 private void buildTimeBlockRunRules(ClockLog beginClockLog, ClockLog endClockLog, TimesheetDocument tdoc, Assignment currentAssignment, String earnCode, Timestamp beginTimestamp, Timestamp endTimestamp) { 210 // New Time Blocks, pointer reference 211 List<TimeBlock> newTimeBlocks = tdoc.getTimeBlocks(); 212 List<TimeBlock> referenceTimeBlocks = new ArrayList<TimeBlock>(newTimeBlocks); 213 for (TimeBlock tb : newTimeBlocks) { 214 referenceTimeBlocks.add(tb.copy()); 215 } 216 217 // Add TimeBlocks after we store our reference object! 218 List<TimeBlock> blocks = TkServiceLocator.getTimeBlockService().buildTimeBlocks( 219 currentAssignment, earnCode, tdoc, beginTimestamp, 220 endTimestamp, BigDecimal.ZERO, BigDecimal.ZERO, true, false, TKContext.getPrincipalId()); 221 222 223 // Add the clock log IDs to the time blocks that were just created. 224 for (TimeBlock block : blocks) { 225 block.setClockLogBeginId(beginClockLog.getTkClockLogId()); 226 block.setClockLogEndId(endClockLog.getTkClockLogId()); 227 } 228 229 newTimeBlocks.addAll(blocks); 230 231 List<Assignment> assignments = tdoc.getAssignments(); 232 List<String> assignmentKeys = new ArrayList<String>(); 233 for (Assignment assignment : assignments) { 234 assignmentKeys.add(assignment.getAssignmentKey()); 235 } 236 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForTimeCalendar(tdoc.getPrincipalId(), tdoc.getAsOfDate(), tdoc.getDocEndDate(), assignmentKeys); 237 238 //reset time block 239 TkServiceLocator.getTimesheetService().resetTimeBlock(newTimeBlocks, tdoc.getAsOfDate()); 240 //apply any rules for this action 241 TkServiceLocator.getTkRuleControllerService().applyRules( 242 TkConstants.ACTIONS.CLOCK_OUT, newTimeBlocks, leaveBlocks, 243 tdoc.getCalendarEntry(), 244 tdoc, tdoc.getPrincipalId() 245 ); 246 247 TkServiceLocator.getTimeBlockService().saveTimeBlocks(referenceTimeBlocks, newTimeBlocks, TKContext.getPrincipalId()); 248 } 249 public MissedPunchDocument getMissedPunchByClockLogId(String clockLogId){ 250 return missedPunchDao.getMissedPunchByClockLogId(clockLogId); 251 252 } 253 254 @Override 255 public void approveMissedPunch(MissedPunchDocument document) { 256 String batchUserPrincipalId = getBatchUserPrincipalId(); 257 258 if (batchUserPrincipalId != null) { 259 String rhid = document.getDocumentNumber(); 260 WorkflowDocument wd = WorkflowDocumentFactory.loadDocument(batchUserPrincipalId, rhid); 261 wd.superUserBlanketApprove("Batch job superuser approving missed punch document."); 262 } else { 263 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME); 264 LOG.error("Could not approve missed punch document due to missing batch user " + principalName); 265 } 266 } 267 268 private String getBatchUserPrincipalId() { 269 String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME); 270 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName); 271 return principal == null ? null : principal.getPrincipalId(); 272 } 273 274 @Override 275 public List<MissedPunchDocument> getMissedPunchDocsByTimesheetDocumentId(String timesheetDocumentId) { 276 return missedPunchDao.getMissedPunchDocsByTimesheetDocumentId(timesheetDocumentId); 277 } 278 279 }