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