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 }