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    }