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 }