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.timesheet.service;
017    
018    import java.math.BigDecimal;
019    import java.sql.Timestamp;
020    import java.util.ArrayList;
021    import java.util.Date;
022    import java.util.LinkedList;
023    import java.util.List;
024    
025    import org.apache.commons.collections.CollectionUtils;
026    import org.apache.commons.lang.StringUtils;
027    import org.apache.log4j.Logger;
028    import org.joda.time.DateTime;
029    import org.kuali.hr.job.Job;
030    import org.kuali.hr.lm.LMConstants;
031    import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
032    import org.kuali.hr.lm.leaveblock.LeaveBlock;
033    import org.kuali.hr.lm.leavepayout.LeavePayout;
034    import org.kuali.hr.lm.timeoff.SystemScheduledTimeOff;
035    import org.kuali.hr.time.assignment.Assignment;
036    import org.kuali.hr.time.calendar.CalendarEntries;
037    import org.kuali.hr.time.principal.PrincipalHRAttributes;
038    import org.kuali.hr.time.service.base.TkServiceLocator;
039    import org.kuali.hr.time.timeblock.TimeBlock;
040    import org.kuali.hr.time.timesheet.TimesheetDocument;
041    import org.kuali.hr.time.util.TKContext;
042    import org.kuali.hr.time.util.TKUser;
043    import org.kuali.hr.time.util.TKUtils;
044    import org.kuali.hr.time.util.TkConstants;
045    import org.kuali.hr.time.workflow.TimesheetDocumentHeader;
046    import org.kuali.rice.core.api.config.property.ConfigContext;
047    import org.kuali.rice.kew.api.KewApiServiceLocator;
048    import org.kuali.rice.kew.api.WorkflowDocument;
049    import org.kuali.rice.kew.api.WorkflowDocumentFactory;
050    import org.kuali.rice.kew.api.exception.WorkflowException;
051    import org.kuali.rice.kew.api.note.Note;
052    import org.kuali.rice.kim.api.identity.Person;
053    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
054    
055    public class TimesheetServiceImpl implements TimesheetService {
056    
057        @SuppressWarnings("unused")
058        private static final Logger LOG = Logger.getLogger(TimesheetServiceImpl.class);
059    
060        @Override
061        public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument) {
062            routeTimesheet(principalId, timesheetDocument, TkConstants.DOCUMENT_ACTIONS.ROUTE);
063        }
064    
065        @Override
066        public void routeTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
067            timesheetAction(action, principalId, timesheetDocument);
068        }
069    
070        @Override
071        public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
072            timesheetAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, timesheetDocument);
073        }
074    
075        @Override
076        public void approveTimesheet(String principalId, TimesheetDocument timesheetDocument, String action) {
077            timesheetAction(action, principalId, timesheetDocument);
078        }
079    
080        @Override
081        public void disapproveTimesheet(String principalId, TimesheetDocument timesheetDocument) {
082            timesheetAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, timesheetDocument);
083        }
084    
085        protected void timesheetAction(String action, String principalId, TimesheetDocument timesheetDocument) {
086            WorkflowDocument wd = null;
087            if (timesheetDocument != null) {
088                String rhid = timesheetDocument.getDocumentId();
089                wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
090    
091                if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
092                    wd.route("Routing for Approval");
093                } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
094                    Note.Builder builder = Note.Builder.create(rhid, principalId);
095                    builder.setCreateDate(new DateTime());
096                    builder.setText("Routed via Employee Approval batch job");
097                    KewApiServiceLocator.getNoteService().createNote(builder.build());
098                    
099                    wd.route("Batch job routing timesheet");
100                } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) {
101                    if (TKContext.getUser().getCurrentTargetRoles().isSystemAdmin() &&
102                            !TKContext.getUser().getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
103                        wd.superUserBlanketApprove("Superuser approving timesheet.");
104                    } else {
105                        wd.approve("Approving timesheet.");
106                    }
107                } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
108                    Note.Builder builder = Note.Builder.create(rhid, principalId);
109                            builder.setCreateDate(new DateTime());
110                            builder.setText("Approved via Supervisor Approval batch job");
111                            KewApiServiceLocator.getNoteService().createNote(builder.build());
112                    
113                    wd.superUserBlanketApprove("Batch job approving timesheet.");
114                } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
115                    if (TKContext.getUser().getCurrentTargetRoles().isSystemAdmin()
116                            && !TKContext.getUser().getCurrentTargetRoles().isApproverForTimesheet(timesheetDocument)) {
117                        wd.superUserDisapprove("Superuser disapproving timesheet.");
118                    } else {
119                        wd.disapprove("Disapproving timesheet.");
120                    }
121                }
122            }
123        }
124    
125        @Override
126        public TimesheetDocument openTimesheetDocument(String principalId, CalendarEntries calendarDates) throws WorkflowException {
127            TimesheetDocument timesheetDocument = null;
128    
129            Date begin = calendarDates.getBeginPeriodDateTime();
130            Date end = calendarDates.getEndPeriodDateTime();
131    
132            TimesheetDocumentHeader header = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(principalId, begin, end);
133    
134            if (header == null) {
135                List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, calendarDates);
136                //TkServiceLocator.getAssignmentService().getAssignments(principalId, TKUtils.getTimelessDate(payCalendarDates.getEndPeriodDate()));
137                if (activeAssignments.size() == 0) {
138                    LOG.warn("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
139                    return null;
140                    //throw new RuntimeException("No active assignments for " + principalId + " for " + calendarDates.getEndPeriodDate());
141                }
142                
143                Person person = KimApiServiceLocator.getPersonService().getPerson(principalId);
144                String principalName = person != null ? person.getName() : StringUtils.EMPTY;
145                String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime()));
146                String timesheetDocumentTitle = TimesheetDocument.TIMESHEET_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + endDateString;
147                
148                timesheetDocument = this.initiateWorkflowDocument(principalId, begin, end, calendarDates, TimesheetDocument.TIMESHEET_DOCUMENT_TYPE, timesheetDocumentTitle);
149                //timesheetDocument.setPayCalendarEntry(calendarDates);
150                //this.loadTimesheetDocumentData(timesheetDocument, principalId, calendarDates);
151                //TODO switch this to scheduled time offs
152                //this.loadHolidaysOnTimesheet(timesheetDocument, principalId, begin, end);
153            } else {
154                timesheetDocument = this.getTimesheetDocument(header.getDocumentId());
155                timesheetDocument.setCalendarEntry(calendarDates);
156            }
157    
158            timesheetDocument.setTimeSummary(TkServiceLocator.getTimeSummaryService().getTimeSummary(timesheetDocument));
159            return timesheetDocument;
160        }
161    
162        public void loadHolidaysOnTimesheet(TimesheetDocument timesheetDocument, String principalId, Date beginDate, Date endDate) {
163            PrincipalHRAttributes principalCalendar = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, new java.sql.Date(beginDate.getTime()));
164            if (principalCalendar != null && StringUtils.isNotEmpty(principalCalendar.getLeavePlan())) {
165                    List<SystemScheduledTimeOff> sstoList = TkServiceLocator.getSysSchTimeOffService()
166                            .getSystemScheduledTimeOffForPayPeriod(principalCalendar.getLeavePlan(), beginDate, endDate);
167                    Assignment sstoAssign = TkServiceLocator.getAssignmentService().getAssignmentToApplyScheduledTimeOff(timesheetDocument, TKUtils.getTimelessDate(endDate));
168                    if (sstoAssign != null) {
169                            for(SystemScheduledTimeOff ssto : sstoList) {
170                      BigDecimal sstoCalcHours = TkServiceLocator.getSysSchTimeOffService().calculateSysSchTimeOffHours(sstoAssign.getJob(), ssto.getAmountofTime());
171                      TimeBlock timeBlock = TkServiceLocator.getTimeBlockService().createTimeBlock(timesheetDocument, new Timestamp(ssto.getScheduledTimeOffDate().getTime()),
172                              new Timestamp(ssto.getScheduledTimeOffDate().getTime()), sstoAssign, TkConstants.HOLIDAY_EARN_CODE, sstoCalcHours, BigDecimal.ZERO, false, false, TKContext.getPrincipalId());
173                      timesheetDocument.getTimeBlocks().add(timeBlock);
174                  }
175                        //If system scheduled time off are loaded will need to save them to the database
176                            if (CollectionUtils.isNotEmpty(sstoList)) {
177                               TkServiceLocator.getTimeBlockService().saveTimeBlocks(new LinkedList<TimeBlock>(), timesheetDocument.getTimeBlocks(), TKContext.getPrincipalId());
178                            }
179                    }
180            }
181        }
182    
183        protected TimesheetDocument initiateWorkflowDocument(String principalId, Date payBeginDate,  Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
184            TimesheetDocument timesheetDocument = null;
185            WorkflowDocument workflowDocument = null;
186    
187            workflowDocument = WorkflowDocumentFactory.createDocument(principalId, documentType, title);
188    
189            String status = workflowDocument.getStatus().getCode();
190            TimesheetDocumentHeader documentHeader = new TimesheetDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
191    
192            documentHeader.setDocumentId(workflowDocument.getDocumentId().toString());
193            documentHeader.setDocumentStatus("I");
194    
195            TkServiceLocator.getTimesheetDocumentHeaderService().saveOrUpdate(documentHeader);
196            timesheetDocument = new TimesheetDocument(documentHeader);
197            timesheetDocument.setCalendarEntry(calendarEntries);
198            loadTimesheetDocumentData(timesheetDocument, principalId, calendarEntries);
199            TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(timesheetDocument, payEndDate);
200    
201            if (TkServiceLocator.getLeaveApprovalService().isActiveAssignmentFoundOnJobFlsaStatus(principalId, TkConstants.FLSA_STATUS_NON_EXEMPT, true)) {
202                    deleteNonApprovedLeaveBlocks(principalId, calendarEntries.getBeginPeriodDate(), calendarEntries.getEndPeriodDate());
203            }
204            
205            return timesheetDocument;
206        }
207        
208        private void deleteNonApprovedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
209            String batchUserPrincipalId = getBatchUserPrincipalId();
210            
211            if (batchUserPrincipalId != null) {
212                    List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
213            
214                    for (LeaveBlock leaveBlock : leaveBlocks) {
215                            if (!StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.APPROVED)) {
216                                    TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
217                            }
218                    }
219            } else {
220                    String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
221                    LOG.error("Could not delete leave request blocks due to missing batch user " + principalName);
222            }
223        }
224        
225        private String getBatchUserPrincipalId() {
226            String principalId = null;
227            
228            String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
229            Person person = KimApiServiceLocator.getPersonService().getPersonByPrincipalName(principalName);
230            if (person != null) {
231                    principalId = person.getPrincipalId();
232            }
233            
234            return principalId;
235        }
236    
237        public List<TimeBlock> getPrevDocumentTimeBlocks(String principalId, Date payBeginDate) {
238            TimesheetDocumentHeader prevTdh = TkServiceLocator.getTimesheetDocumentHeaderService().getPreviousDocumentHeader(principalId, payBeginDate);
239            if (prevTdh == null) {
240                return new ArrayList<TimeBlock>();
241            }
242            return TkServiceLocator.getTimeBlockService().getTimeBlocks(prevTdh.getDocumentId());
243        }
244    
245        @Override
246        public TimesheetDocument getTimesheetDocument(String documentId) {
247            TimesheetDocument timesheetDocument = null;
248            TimesheetDocumentHeader tdh = TkServiceLocator.getTimesheetDocumentHeaderService().getDocumentHeader(documentId);
249    
250            if (tdh != null) {
251                timesheetDocument = new TimesheetDocument(tdh);
252                CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(tdh.getPrincipalId(), tdh.getEndDate(), TkConstants.PAY_CALENDAR_TYPE);
253                loadTimesheetDocumentData(timesheetDocument, tdh.getPrincipalId(), pce);
254    
255                timesheetDocument.setCalendarEntry(pce);
256            } else {
257                throw new RuntimeException("Could not find TimesheetDocumentHeader for DocumentID: " + documentId);
258            }
259            return timesheetDocument;
260        }
261    
262        protected void loadTimesheetDocumentData(TimesheetDocument tdoc, String principalId, CalendarEntries payCalEntry) {
263            List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForTimeCalendar(principalId, payCalEntry);
264            List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, TKUtils.getTimelessDate(payCalEntry.getEndPeriodDate()));
265            List<TimeBlock> timeBlocks = TkServiceLocator.getTimeBlockService().getTimeBlocks(tdoc.getDocumentHeader().getDocumentId());
266    
267            tdoc.setAssignments(assignments);
268            tdoc.setJobs(jobs);
269            tdoc.setTimeBlocks(timeBlocks);
270        }
271    
272        public boolean isSynchronousUser() {
273            List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignments(TKUser.getCurrentTargetPerson().getPrincipalId(), TKUtils.getCurrentDate());
274            boolean isSynchronousUser = true;
275            for (Assignment assignment : assignments) {
276                isSynchronousUser &= assignment.isSynchronous();
277            }
278            return isSynchronousUser;
279        }
280    
281        //this is an admin function used for testing
282        public void deleteTimesheet(String documentId) {
283            TkServiceLocator.getTimeBlockService().deleteTimeBlocksAssociatedWithDocumentId(documentId);
284            TkServiceLocator.getTimesheetDocumentHeaderService().deleteTimesheetHeader(documentId);
285        }
286    
287        public TimeBlock resetWorkedHours(TimeBlock timeBlock) {
288            if (timeBlock.getBeginTime() != null && timeBlock.getEndTime() != null && StringUtils.equals(timeBlock.getEarnCodeType(), TkConstants.EARN_CODE_TIME)) {
289                BigDecimal hours = TKUtils.getHoursBetween(timeBlock.getBeginTime().getTime(), timeBlock.getEndTime().getTime());
290                timeBlock.setHours(hours);
291            }
292            return timeBlock;
293        }
294    
295        @Override
296        public void resetTimeBlock(List<TimeBlock> timeBlocks) {
297            for (TimeBlock tb : timeBlocks) {
298                resetWorkedHours(tb);
299            }
300            TkServiceLocator.getTimeBlockService().resetTimeHourDetail(timeBlocks);
301        }
302    
303            @Override
304            public boolean isReadyToApprove(TimesheetDocument document) {
305            if (document == null) {
306                return false;
307            }
308            List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
309                            document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
310            leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
311                            document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
312            for(LeaveBlock lb : leaveBlocks) {
313                    if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
314                                    !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
315                            return false;
316            }
317            return true;
318    /*        List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
319                    document.getCalendarEntry().getBeginPeriodDate(),
320                    document.getCalendarEntry().getEndPeriodDate());
321            if (!CollectionUtils.isEmpty(balanceTransfers))   {
322                    for(BalanceTransfer balanceTransfer : balanceTransfers) {
323                            if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
324                                    return false;
325                        if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
326                                && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
327                            return false;
328                        }
329                    }
330            }
331            List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
332                            document.getCalendarEntry().getBeginPeriodDate(),
333                            document.getCalendarEntry().getEndPeriodDate());
334            if (!CollectionUtils.isEmpty(leavePayouts)) {
335                    for(LeavePayout payout : leavePayouts) {
336                            if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
337                                    return false;
338                        if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
339                                && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
340                            return false;
341                        }
342                    }
343            }
344            return true;*/
345            }
346    
347    }