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