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