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.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
243 @Override
244 public List<MissedPunchDocument> getMissedPunchDocsByBatchJobEntry(BatchJobEntry batchJobEntry) {
245 return missedPunchDao.getMissedPunchDocsByBatchJobEntry(batchJobEntry);
246 }
247
248 @Override
249 public List<MissedPunchDocument> getMissedPunchDocsByTimesheetDocumentId(String timesheetDocumentId) {
250 return missedPunchDao.getMissedPunchDocsByTimesheetDocumentId(timesheetDocumentId);
251 }
252
253 }