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;
017    
018    import java.sql.Timestamp;
019    import java.text.ParseException;
020    import java.text.SimpleDateFormat;
021    import java.util.Date;
022    import java.util.HashSet;
023    import java.util.Set;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.joda.time.DateTime;
027    import org.joda.time.DateTimeZone;
028    import org.joda.time.LocalTime;
029    import org.kuali.hr.time.clocklog.ClockLog;
030    import org.kuali.hr.time.service.base.TkServiceLocator;
031    import org.kuali.hr.time.timesheet.TimesheetDocument;
032    import org.kuali.hr.time.util.TKUtils;
033    import org.kuali.hr.time.util.TkConstants;
034    import org.kuali.rice.krad.document.Document;
035    import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
036    import org.kuali.rice.krad.util.GlobalVariables;
037    
038    public class MissedPunchValidation extends TransactionalDocumentRuleBase {
039    
040        /**
041         * Checks the provided MissedPunch for a valid ClockAction.
042         *
043         * @param mp The MissedPunch we are validating.
044         * @param lastClock The ClockLog entry that happened before the one we want to create.
045         *
046         * @return true if valid, false otherwise.
047         */
048        boolean validateClockAction(MissedPunchDocument mp, ClockLog lastClock) {
049            boolean valid = true;
050            Set<String> validActions = (lastClock != null) ? TkConstants.CLOCK_ACTION_TRANSITION_MAP.get(lastClock.getClockAction()) : new HashSet<String>();
051    
052            // if a clockIn/lunchIn has been put in by missed punch, do not allow missed punch for clockOut/LunchOut
053            // missed punch can only be used on a tiemblock once.
054            if(lastClock != null 
055                            && (lastClock.getClockAction().equals(TkConstants.CLOCK_IN) || lastClock.getClockAction().equals(TkConstants.LUNCH_IN))) {
056                    MissedPunchDocument mpd = TkServiceLocator.getMissedPunchService().getMissedPunchByClockLogId(lastClock.getTkClockLogId());
057                    if(mpd != null) {
058                            GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.onlyOne.action");
059                        return false;
060                    }
061            }
062            if (!StringUtils.equals("A", mp.getDocumentStatus()) && !validActions.contains(mp.getClockAction())) {
063                GlobalVariables.getMessageMap().putError("document.clockAction", "clock.mp.invalid.action");
064                valid = false;
065            }
066            
067            return valid;
068        }
069    
070        /**
071         * Checks whether the provided MissedPunch has a valid time based on the
072         * previous ClockLog.
073         *
074         * @param mp The MissedPunch we are validating.
075         * @param lastClock The ClockLog entry that happened before the one we want to create.
076         *
077         * @return true if valid, false otherwise.
078         * @throws ParseException 
079         */
080        boolean validateClockTime(MissedPunchDocument mp, ClockLog lastClock) throws ParseException {
081            boolean valid = true;
082    
083            if (lastClock == null)
084                return valid;
085            
086            //Missed Action Date and Missed Action Time are required fields. KPME-1853
087            if(mp.getActionTime() == null || mp.getActionDate() == null)
088                    return false;
089            
090            DateTime clockLogDateTime = new DateTime(lastClock.getClockTimestamp().getTime());
091            DateTime boundaryMax = clockLogDateTime.plusDays(1);
092            DateTime nowTime = new DateTime(TKUtils.getCurrentDate());
093            
094            SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
095            String s = formatter.format(mp.getActionDate());
096                    Date tempDate = formatter.parse(s);
097                    Timestamp dateLocal = new Timestamp(tempDate.getTime());
098            LocalTime timeLocal = new LocalTime(mp.getActionTime().getTime());
099            DateTime actionDateTime = new DateTime(dateLocal.getTime());
100            actionDateTime = actionDateTime.plus(timeLocal.getMillisOfDay());
101    
102            // convert the action time to the system zone 
103            Timestamp ts = new Timestamp(actionDateTime.getMillis());
104            ClockLog lastLog = TkServiceLocator.getClockLogService().getLastClockLog(mp.getPrincipalId());
105            Long zoneOffset = TkServiceLocator.getTimezoneService().getTimezoneOffsetFromServerTime(DateTimeZone.forID(lastLog.getClockTimestampTimezone()));
106            Timestamp actionTime = new Timestamp(ts.getTime()-zoneOffset);
107            DateTime newDateTime = new DateTime(actionTime.getTime());
108    
109            // if date is a future date
110            if(actionDateTime.getYear()> nowTime.getYear()
111                            || (actionDateTime.getYear()==nowTime.getYear() && actionDateTime.getDayOfYear() > nowTime.getDayOfYear())) {
112                    GlobalVariables.getMessageMap().putError("document.actionDate", "clock.mp.future.date");
113                    return false;
114            }
115    
116            // if time is a future time
117            if(actionDateTime.getMillis() > nowTime.getMillis()) {
118                    GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.future.time");
119                    return false;
120            }
121            
122            if ( ((!StringUtils.equals(lastClock.getClockAction(), TkConstants.CLOCK_OUT) && actionDateTime.isAfter(boundaryMax)) 
123                            || newDateTime.isBefore(clockLogDateTime)) && StringUtils.equals(mp.getDocumentStatus(),"R")) {
124                    GlobalVariables.getMessageMap().putError("document.actionTime", "clock.mp.invalid.datetime");
125                valid = false;
126            }
127    
128            return valid;
129        }
130     
131        // do not allow a missed punch if the time sheet document is enroute or final
132        boolean validateTimeSheet(MissedPunchDocument mp) {
133            boolean valid = true;
134            TimesheetDocument tsd = TkServiceLocator.getTimesheetService().getTimesheetDocument(mp.getTimesheetDocumentId());
135            if(tsd != null 
136                            && (tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.ENROUTE) 
137                                            || tsd.getDocumentHeader().getDocumentStatus().equals(TkConstants.ROUTE_STATUS.FINAL))) {
138                    GlobalVariables.getMessageMap().putError("document.timesheetDocumentId", "clock.mp.invalid.timesheet");
139                    valid = false;
140            }
141            
142            return valid;
143        }
144            @Override
145            public boolean processRouteDocument(Document document) {
146            boolean ret = super.processRouteDocument(document);
147            MissedPunchDocument mpDoc = (MissedPunchDocument)document;
148            if(!validateTimeSheet(mpDoc)) {
149                    return false;
150            }
151            ClockLog lastClock = TkServiceLocator.getClockLogService().getLastClockLog(mpDoc.getPrincipalId());
152            ret &= validateClockAction(mpDoc, lastClock);
153            try {
154                            ret &= validateClockTime(mpDoc, lastClock);
155                    } catch (ParseException e) {
156                            // TODO Auto-generated catch block
157                            e.printStackTrace();
158                    }
159            
160            return ret;
161            }
162    }