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    }