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