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