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