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