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