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.timeblock.service;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.sql.Timestamp;
021    import java.util.ArrayList;
022    import java.util.List;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.log4j.Logger;
026    import org.joda.time.DateTime;
027    import org.joda.time.DateTimeConstants;
028    import org.joda.time.DateTimeZone;
029    import org.joda.time.Interval;
030    import org.kuali.hr.job.Job;
031    import org.kuali.hr.lm.earncodesec.EarnCodeSecurity;
032    import org.kuali.hr.time.assignment.Assignment;
033    import org.kuali.hr.time.earncode.EarnCode;
034    import org.kuali.hr.time.paytype.PayType;
035    import org.kuali.hr.time.service.base.TkServiceLocator;
036    import org.kuali.hr.time.timeblock.TimeBlock;
037    import org.kuali.hr.time.timeblock.TimeBlockHistory;
038    import org.kuali.hr.time.timeblock.TimeHourDetail;
039    import org.kuali.hr.time.timeblock.dao.TimeBlockDao;
040    import org.kuali.hr.time.timesheet.TimesheetDocument;
041    import org.kuali.hr.time.util.TKContext;
042    import org.kuali.hr.time.util.TKUtils;
043    import org.kuali.hr.time.util.TkConstants;
044    import org.kuali.rice.krad.service.KRADServiceLocator;
045    import org.kuali.rice.krad.util.GlobalVariables;
046    
047    public class TimeBlockServiceImpl implements TimeBlockService {
048    
049        private static final Logger LOG = Logger.getLogger(TimeBlockServiceImpl.class);
050        private TimeBlockDao timeBlockDao;
051    
052        public void setTimeBlockDao(TimeBlockDao timeBlockDao) {
053            this.timeBlockDao = timeBlockDao;
054        }
055    
056        //This function is used to build timeblocks that span days
057        public List<TimeBlock> buildTimeBlocksSpanDates(Assignment assignment, String earnCode, TimesheetDocument timesheetDocument,
058                                                        Timestamp beginTimestamp, Timestamp endTimestamp, BigDecimal hours, BigDecimal amount, 
059                                                        Boolean isClockLogCreated, Boolean isLunchDeleted, String spanningWeeks, String userPrincipalId) {
060            DateTimeZone zone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
061            DateTime beginDt = new DateTime(beginTimestamp.getTime(), zone);
062            DateTime endDt = beginDt.toLocalDate().toDateTime((new DateTime(endTimestamp.getTime(), zone)).toLocalTime(), zone);
063            if (endDt.isBefore(beginDt)) endDt = endDt.plusDays(1);
064            
065            List<Interval> dayInt = TKUtils.getDaySpanForCalendarEntry(timesheetDocument.getCalendarEntry());
066            TimeBlock firstTimeBlock = new TimeBlock();
067            List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>();
068            for (Interval dayIn : dayInt) {
069                if (dayIn.contains(beginDt)) {
070                    if (dayIn.contains(endDt) || dayIn.getEnd().equals(endDt)) {
071                            // KPME-1446 if "Include weekends" check box is checked, don't add Sat and Sun to the timeblock list
072                            if (StringUtils.isEmpty(spanningWeeks) && 
073                                    (dayIn.getStart().getDayOfWeek() == DateTimeConstants.SATURDAY ||dayIn.getStart().getDayOfWeek() == DateTimeConstants.SUNDAY)) {
074                                    // do nothing
075                            } else {
076                            firstTimeBlock = createTimeBlock(timesheetDocument, beginTimestamp, new Timestamp(endDt.getMillis()), assignment, earnCode, hours, amount, false, isLunchDeleted, userPrincipalId);
077                            lstTimeBlocks.add(firstTimeBlock);                              
078                            }
079                    } else {
080                        //TODO move this to prerule validation
081                        //throw validation error if this case met error
082                    }
083                }
084            }
085    
086            DateTime endTime = new DateTime(endTimestamp.getTime(), zone);
087            DateTime endOfFirstDay = new DateTime(firstTimeBlock.getEndTimestamp(), zone);
088            long diffInMillis = endOfFirstDay.minus(beginDt.getMillis()).getMillis();
089            DateTime currTime = beginDt.plusDays(1);
090            while (currTime.isBefore(endTime) || currTime.isEqual(endTime)) {
091                    // KPME-1446 if "Include weekends" check box is checked, don't add Sat and Sun to the timeblock list
092                    if (StringUtils.isEmpty(spanningWeeks) && 
093                            (currTime.getDayOfWeek() == DateTimeConstants.SATURDAY || currTime.getDayOfWeek() == DateTimeConstants.SUNDAY)) {
094                            // do nothing
095                    } else {
096                        Timestamp begin = new Timestamp(currTime.getMillis());
097                        Timestamp end = new Timestamp((currTime.plus(diffInMillis).getMillis()));
098                        TimeBlock tb = createTimeBlock(timesheetDocument, begin, end, assignment, earnCode, hours, amount, false, isLunchDeleted, userPrincipalId);
099                        lstTimeBlocks.add(tb);
100                    }
101                    currTime = currTime.plusDays(1);
102            }
103            return lstTimeBlocks;
104        }
105    
106    
107        public List<TimeBlock> buildTimeBlocks(Assignment assignment, String earnCode, TimesheetDocument timesheetDocument,
108                                               Timestamp beginTimestamp, Timestamp endTimestamp, BigDecimal hours, BigDecimal amount, 
109                                               Boolean isClockLogCreated, Boolean isLunchDeleted, String userPrincipalId) {
110    
111            //Create 1 or many timeblocks if the span of timeblocks exceed more than one
112            //day that is determined by pay period day(24 hrs + period begin date)
113            Interval firstDay = null;
114            List<Interval> dayIntervals = TKUtils.getDaySpanForCalendarEntry(timesheetDocument.getCalendarEntry());
115            List<TimeBlock> lstTimeBlocks = new ArrayList<TimeBlock>();
116            Timestamp beginTemp = beginTimestamp;
117    
118            for (Interval dayInt : dayIntervals) {
119                    // the time period spans more than one day
120                if (firstDay != null) {
121                    if(!dayInt.contains(endTimestamp.getTime())){
122                            beginTemp = new Timestamp(dayInt.getStartMillis());
123                    } else if((dayInt.getStartMillis() - endTimestamp.getTime()) != 0){
124                            TimeBlock tb = createTimeBlock(timesheetDocument, new Timestamp(dayInt.getStartMillis()), endTimestamp, assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted, userPrincipalId);
125                            lstTimeBlocks.add(tb);
126                            break;
127                    }                       
128                }
129                if (dayInt.contains(beginTemp.getTime())) {
130                    firstDay = dayInt;
131                    // KPME-361
132                    // added a condition to handle the time block which ends at 12a, e.g. a 10p-12a timeblock
133                    // this is the same fix as TkTimeBlockAggregate
134                    if (dayInt.contains(endTimestamp.getTime()) || (endTimestamp.getTime() == dayInt.getEnd().getMillis())) {
135                        //create one timeblock if contained in one day interval
136                            TimeBlock tb = createTimeBlock(timesheetDocument, beginTemp, endTimestamp, assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted, userPrincipalId);
137                        tb.setBeginTimestamp(beginTemp);
138                        tb.setEndTimestamp(endTimestamp);
139                        lstTimeBlocks.add(tb);
140                        break;
141                    } else {
142                        // create a timeblock that wraps the 24 hr day
143                            TimeBlock tb = createTimeBlock(timesheetDocument, beginTemp, new Timestamp(dayInt.getEndMillis()), assignment, earnCode, hours, amount, isClockLogCreated, isLunchDeleted, userPrincipalId);
144                        tb.setBeginTimestamp(beginTemp);
145                        tb.setEndTimestamp(new Timestamp(firstDay.getEndMillis()));
146                        lstTimeBlocks.add(tb);
147                    }
148                }
149            }
150            return lstTimeBlocks;
151        }
152    
153        public void saveTimeBlocks(List<TimeBlock> oldTimeBlocks, List<TimeBlock> newTimeBlocks, String userPrincipalId) {
154            List<TimeBlock> alteredTimeBlocks = new ArrayList<TimeBlock>();
155            for (TimeBlock tb : newTimeBlocks) {
156                boolean persist = true;
157                for (TimeBlock tbOld : oldTimeBlocks) {
158                    if (tb.equals(tbOld)) {
159                        persist = false;
160                        break;
161                    }
162                }
163                if (persist) {
164                    alteredTimeBlocks.add(tb);
165                }
166            }
167            
168            for (TimeBlock timeBlock : alteredTimeBlocks) {
169                TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(timeBlock.getTkTimeBlockId());
170                timeBlock.setUserPrincipalId(userPrincipalId);
171            }
172            
173            List<TimeBlock> savedTimeBlocks = (List<TimeBlock>) KRADServiceLocator.getBusinessObjectService().save(alteredTimeBlocks);
174            
175            for (TimeBlock timeBlock : savedTimeBlocks) {
176                timeBlock.setTimeBlockHistories(createTimeBlockHistories(timeBlock, TkConstants.ACTIONS.ADD_TIME_BLOCK));
177                KRADServiceLocator.getBusinessObjectService().save(timeBlock.getTimeBlockHistories());
178            }
179        }
180    
181        public void saveTimeBlocks(List<TimeBlock> tbList) {
182                     for (TimeBlock tb : tbList) {
183                     TkServiceLocator.getTimeHourDetailService().removeTimeHourDetails(tb.getTkTimeBlockId());
184                     timeBlockDao.saveOrUpdate(tb);
185                     for(TimeBlockHistory tbh : tb.getTimeBlockHistories()){
186                             TkServiceLocator.getTimeBlockHistoryService().saveTimeBlockHistory(tbh);
187                     }
188                 }
189        }
190        
191        public void updateTimeBlock(TimeBlock tb) {
192                     timeBlockDao.saveOrUpdate(tb);
193        }
194    
195    
196        public TimeBlock createTimeBlock(TimesheetDocument timesheetDocument, Timestamp beginTime, Timestamp endTime, Assignment assignment, String earnCode, BigDecimal hours, BigDecimal amount, Boolean clockLogCreated, Boolean lunchDeleted, String userPrincipalId) {
197            DateTimeZone timezone = TkServiceLocator.getTimezoneService().getUserTimezoneWithFallback();
198            EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, timesheetDocument.getAsOfDate());
199    
200            TimeBlock tb = new TimeBlock();
201            tb.setDocumentId(timesheetDocument.getDocumentHeader().getDocumentId());
202            tb.setPrincipalId(timesheetDocument.getPrincipalId());
203            tb.setJobNumber(assignment.getJobNumber());
204            tb.setWorkArea(assignment.getWorkArea());
205            tb.setTask(assignment.getTask());
206            tb.setEarnCode(earnCode);
207            tb.setBeginTimestamp(beginTime);
208            tb.setBeginTimestampTimezone(timezone.getID());
209            tb.setEndTimestamp(endTime);
210            tb.setEndTimestampTimezone(timezone.getID());
211            tb.setBeginTimeDisplay(new DateTime(tb.getBeginTimestamp(), timezone));
212            tb.setEndTimeDisplay(new DateTime(tb.getEndTimestamp(), timezone));
213            // only calculate the hours from the time fields if the passed in hour is zero
214            if(hours == null || hours.compareTo(BigDecimal.ZERO) == 0) {
215                    hours = TKUtils.getHoursBetween(beginTime.getTime(), endTime.getTime());
216            }
217            tb.setAmount(amount);
218            //If earn code has an inflate min hours check if it is greater than zero
219            //and compare if the hours specified is less than min hours awarded for this
220            //earn code
221            if (earnCodeObj.getInflateMinHours() != null) {
222                if ((earnCodeObj.getInflateMinHours().compareTo(BigDecimal.ZERO) != 0) &&
223                        earnCodeObj.getInflateMinHours().compareTo(hours) > 0) {
224                    hours = earnCodeObj.getInflateMinHours();
225                }
226            }
227            //If earn code has an inflate factor multiple hours specified by the factor
228            if (earnCodeObj.getInflateFactor() != null) {
229                if ((earnCodeObj.getInflateFactor().compareTo(new BigDecimal(1.0)) != 0)
230                            && (earnCodeObj.getInflateFactor().compareTo(BigDecimal.ZERO)!= 0) ) {
231                    hours = earnCodeObj.getInflateFactor().multiply(hours, TkConstants.MATH_CONTEXT).setScale(TkConstants.BIG_DECIMAL_SCALE);
232                }
233            }
234    
235            tb.setEarnCodeType(earnCodeObj.getEarnCodeType());
236            tb.setHours(hours);
237            tb.setClockLogCreated(clockLogCreated);
238            tb.setUserPrincipalId(userPrincipalId);
239            tb.setTimestamp(new Timestamp(System.currentTimeMillis()));
240            tb.setLunchDeleted(lunchDeleted);
241    
242            tb.setTimeHourDetails(this.createTimeHourDetails(tb.getEarnCode(), tb.getHours(), tb.getAmount(), tb.getTkTimeBlockId()));
243    
244            return tb;
245        }
246    
247        public TimeBlock getTimeBlock(String tkTimeBlockId) {
248            return timeBlockDao.getTimeBlock(tkTimeBlockId);
249        }
250    
251        @Override
252        public void deleteTimeBlock(TimeBlock timeBlock) {
253            timeBlockDao.deleteTimeBlock(timeBlock);
254    
255        }
256    
257        public void resetTimeHourDetail(List<TimeBlock> origTimeBlocks) {
258            for (TimeBlock tb : origTimeBlocks) {
259                tb.setTimeHourDetails(createTimeHourDetails(tb.getEarnCode(), tb.getHours(), tb.getAmount(), tb.getTkTimeBlockId()));
260                //reset time block history details
261                for(TimeBlockHistory tbh : tb.getTimeBlockHistories()) {
262                    TkServiceLocator.getTimeBlockHistoryService().addTimeBlockHistoryDetails(tbh,tb);
263                }
264            }
265        }
266    
267        private List<TimeHourDetail> createTimeHourDetails(String earnCode, BigDecimal hours, BigDecimal amount, String timeBlockId) {
268            List<TimeHourDetail> timeHourDetails = new ArrayList<TimeHourDetail>();
269    
270            TimeHourDetail timeHourDetail = new TimeHourDetail();
271            timeHourDetail.setEarnCode(earnCode);
272            timeHourDetail.setHours(hours);
273            timeHourDetail.setAmount(amount);
274            timeHourDetail.setTkTimeBlockId(timeBlockId);
275            timeHourDetails.add(timeHourDetail);
276    
277            return timeHourDetails;
278        }
279    
280        public List<TimeBlockHistory> createTimeBlockHistories(TimeBlock tb, String actionHistory) {
281            List<TimeBlockHistory> tbhs = new ArrayList<TimeBlockHistory>();
282    
283            TimeBlockHistory tbh = new TimeBlockHistory(tb);
284            tbh.setActionHistory(actionHistory);
285            // add time block history details to this time block history
286            TkServiceLocator.getTimeBlockHistoryService().addTimeBlockHistoryDetails(tbh, tb);
287            
288            tbhs.add(tbh);
289    
290            return tbhs;
291        }
292        
293        // This method now translates time based on timezone settings.
294        //
295        public List<TimeBlock> getTimeBlocks(String documentId) {
296            List<TimeBlock> timeBlocks = timeBlockDao.getTimeBlocks(documentId);
297            TkServiceLocator.getTimezoneService().translateForTimezone(timeBlocks);
298            for(TimeBlock tb : timeBlocks) {
299                String earnCodeType = TkServiceLocator.getEarnCodeService().getEarnCodeType(tb.getEarnCode(), new java.sql.Date(tb.getBeginTimestamp().getTime()));
300                tb.setEarnCodeType(earnCodeType);
301            }
302    
303            return timeBlocks;
304        }
305    
306        public List<TimeBlock> getTimeBlocksForAssignment(Assignment assign) {
307            List<TimeBlock> timeBlocks = new ArrayList<TimeBlock>();
308            if(assign != null) {
309                    timeBlocks = timeBlockDao.getTimeBlocksForAssignment(assign);
310            }
311            TkServiceLocator.getTimezoneService().translateForTimezone(timeBlocks);
312             for(TimeBlock tb : timeBlocks) {
313                 String earnCodeType = TkServiceLocator.getEarnCodeService().getEarnCodeType(tb.getEarnCode(), new java.sql.Date(tb.getBeginTimestamp().getTime()));
314                 tb.setEarnCodeType(earnCodeType);
315             }
316            return timeBlocks;
317        }
318    
319    
320            @Override
321            public void deleteTimeBlocksAssociatedWithDocumentId(String documentId) {
322                    timeBlockDao.deleteTimeBlocksAssociatedWithDocumentId(documentId);
323            }
324    
325            @Override
326            // figure out if the user has permission to edit/delete the time block
327            public Boolean isTimeBlockEditable(TimeBlock tb) {
328                    String userId = GlobalVariables.getUserSession().getPrincipalId();
329    
330            if(userId != null) {
331    
332                            if(TKContext.getUser().isSystemAdmin()) {
333                                    return true;
334                            }
335    
336                            if(TKContext.getUser().isTimesheetApprover() && TKContext.getUser().getApproverWorkAreas().contains(tb.getWorkArea()) 
337                                            || TKContext.getUser().isTimesheetReviewer() && TKContext.getUser().getReviewerWorkAreas().contains(tb.getWorkArea())) {
338                                    Job job = TkServiceLocator.getJobService().getJob(TKContext.getTargetPrincipalId(),tb.getJobNumber(), tb.getEndDate());
339                                    PayType payType = TkServiceLocator.getPayTypeService().getPayType(job.getHrPayType(), tb.getEndDate());
340                                    if(StringUtils.equals(payType.getRegEarnCode(), tb.getEarnCode())){
341                                            return true;
342                                    }
343    
344                                    List<EarnCodeSecurity> deptEarnCodes = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), tb.getEndDate());
345                                    for(EarnCodeSecurity dec : deptEarnCodes){
346                                            if(dec.isApprover() && StringUtils.equals(dec.getEarnCode(), tb.getEarnCode())){
347                                                    return true;
348                                            }
349                                    }
350                            }
351    
352                            if(userId.equals(TKContext.getTargetPrincipalId())) {
353                                    Job job = TkServiceLocator.getJobService().getJob(TKContext.getTargetPrincipalId(),tb.getJobNumber(), tb.getEndDate());
354                                    PayType payType = TkServiceLocator.getPayTypeService().getPayType(job.getHrPayType(), tb.getEndDate());
355                                    if(StringUtils.equals(payType.getRegEarnCode(), tb.getEarnCode())){
356                                            return true;
357                                    }
358    
359                                    List<EarnCodeSecurity> deptEarnCodes = TkServiceLocator.getEarnCodeSecurityService().getEarnCodeSecurities(job.getDept(), job.getHrSalGroup(), job.getLocation(), tb.getEndDate());
360                                    for(EarnCodeSecurity dec : deptEarnCodes){
361                                            if(dec.isEmployee() && StringUtils.equals(dec.getEarnCode(), tb.getEarnCode())){
362                                                    return true;
363                                            }
364                                    }
365                                    // if the user is the creator of this time block
366                            }
367    
368    
369                    }
370                    return false;
371            }
372    
373            @Override
374            public List<TimeBlock> getTimeBlocksForClockLogEndId(String tkClockLogId) {
375                    return timeBlockDao.getTimeBlocksForClockLogEndId(tkClockLogId);
376            }
377            @Override
378            public List<TimeBlock> getTimeBlocksForClockLogBeginId(String tkClockLogId) {
379                    return timeBlockDao.getTimeBlocksForClockLogBeginId(tkClockLogId);
380            }
381    
382            public List<TimeBlock> getTimeBlocks(){
383                    return timeBlockDao.getTimeBlocks();
384            }
385            
386            public List<TimeBlock> getLatestEndTimestamp(){
387                    return timeBlockDao.getLatestEndTimestamp();
388            }
389    
390        @Override
391        public List<TimeBlock> getOvernightTimeBlocks(String clockLogEndId) {
392            return timeBlockDao.getOvernightTimeBlocks(clockLogEndId);
393        }
394        
395        @Override
396        public void deleteLunchDeduction(String tkTimeHourDetailId) {
397            TimeHourDetail thd = TkServiceLocator.getTimeHourDetailService().getTimeHourDetail(tkTimeHourDetailId);
398            TimeBlock tb = getTimeBlock(thd.getTkTimeBlockId());
399            
400            // mark the lunch deleted as Y
401            tb.setLunchDeleted(true);
402            // save the change
403            timeBlockDao.saveOrUpdate(tb);
404            // remove the related time hour detail row with the lunch deduction
405            TkServiceLocator.getTimeHourDetailService().removeTimeHourDetail(thd.getTkTimeHourDetailId());
406        }
407        @Override
408        public List<TimeBlock> getTimeBlocksWithEarnCode(String earnCode, Date effDate) {
409            return timeBlockDao.getTimeBlocksWithEarnCode(earnCode, effDate);
410        }
411    }