View Javadoc

1   /**
2    * Copyright 2004-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.hr.time.missedpunch.service;
17  
18  import java.math.BigDecimal;
19  import java.sql.Timestamp;
20  import java.util.ArrayList;
21  import java.util.List;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.apache.log4j.Logger;
25  import org.joda.time.DateTime;
26  import org.joda.time.DateTimeZone;
27  import org.joda.time.LocalTime;
28  import org.kuali.hr.lm.leaveblock.LeaveBlock;
29  import org.kuali.hr.time.assignment.Assignment;
30  import org.kuali.hr.time.calendar.CalendarEntries;
31  import org.kuali.hr.time.clocklog.ClockLog;
32  import org.kuali.hr.time.missedpunch.MissedPunchDocument;
33  import org.kuali.hr.time.missedpunch.dao.MissedPunchDao;
34  import org.kuali.hr.time.service.base.TkServiceLocator;
35  import org.kuali.hr.time.timeblock.TimeBlock;
36  import org.kuali.hr.time.timesheet.TimesheetDocument;
37  import org.kuali.hr.time.util.TKContext;
38  import org.kuali.hr.time.util.TKUtils;
39  import org.kuali.hr.time.util.TkConstants;
40  import org.kuali.rice.core.api.config.property.ConfigContext;
41  import org.kuali.rice.kew.api.WorkflowDocument;
42  import org.kuali.rice.kew.api.WorkflowDocumentFactory;
43  import org.kuali.rice.kim.api.identity.principal.Principal;
44  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
45  import org.kuali.rice.krad.service.KRADServiceLocator;
46  import org.kuali.rice.krad.util.GlobalVariables;
47  
48  public class MissedPunchServiceImpl implements MissedPunchService {
49  	
50  	private static final Logger LOG = Logger.getLogger(MissedPunchServiceImpl.class);
51  
52      MissedPunchDao missedPunchDao;
53  
54      @Override
55      public MissedPunchDocument getMissedPunchByRouteHeader(String headerId) {
56          return missedPunchDao.getMissedPunchByRouteHeader(headerId);
57      }
58  
59      public void setMissedPunchDao(MissedPunchDao missedPunchDao) {
60          this.missedPunchDao = missedPunchDao;
61      }
62  
63      @Override
64      public void updateClockLogAndTimeBlockIfNecessary(MissedPunchDocument missedPunch) {
65          java.util.Date actionDate = missedPunch.getActionDate();
66          java.sql.Time actionTime = missedPunch.getActionTime();
67  
68          LocalTime actionTimeLocal = new LocalTime(actionTime.getTime());
69          DateTime actionDateTime = new DateTime(actionDate.getTime());
70  
71          actionDateTime = actionDateTime.plus(actionTimeLocal.getMillisOfDay());
72  
73          ClockLog cl = TkServiceLocator.getClockLogService().getClockLog(missedPunch.getTkClockLogId());
74          // in case the missedpunch doc has an valication error but the clockLog has been changed at certain time
75          // need to reset the clock log back to the original one
76          if(cl == null) {
77          	MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByRouteHeader(missedPunch.getDocumentNumber());
78          	if(mpd != null) {
79          		missedPunch.setTkClockLogId(mpd.getTkClockLogId());
80          		cl = TkServiceLocator.getClockLogService().getClockLog(missedPunch.getTkClockLogId());
81          	}
82          }
83  
84          if(cl != null && cl.getClockTimestamp().compareTo(new Timestamp(actionDateTime.getMillis())) != 0){
85          	//change occurred between the initial save and the approver
86          	//inactivate all the previous timeblocks and delete clock logs
87          	String logEndId = null;
88          	String logBeginId = null;
89          	List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocksForClockLogEndId(cl.getTkClockLogId());
90          	if(timeBlocks.isEmpty()) {
91          		// get timeBlock with the Clock Log as the clock_log_begin_id
92          		timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocksForClockLogBeginId(cl.getTkClockLogId());
93          		if(!timeBlocks.isEmpty()) {
94          			logEndId = timeBlocks.get(0).getClockLogEndId();
95          		}
96          	} else {
97          		logBeginId = timeBlocks.get(0).getClockLogBeginId();	// new time blocks should keep the original clockLogBeginId
98          	}
99          	
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 }