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.lm.leavecalendar.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.leavecalendar.LeaveCalendarDocument;
026    import org.kuali.hr.lm.leavecalendar.dao.LeaveCalendarDao;
027    import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
028    import org.kuali.hr.lm.workflow.LeaveRequestDocument;
029    import org.kuali.hr.time.assignment.Assignment;
030    import org.kuali.hr.time.calendar.CalendarEntries;
031    import org.kuali.hr.time.service.base.TkServiceLocator;
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.rice.core.api.config.property.ConfigContext;
037    import org.kuali.rice.kew.api.KewApiServiceLocator;
038    import org.kuali.rice.kew.api.WorkflowDocument;
039    import org.kuali.rice.kew.api.WorkflowDocumentFactory;
040    import org.kuali.rice.kew.api.exception.WorkflowException;
041    import org.kuali.rice.kew.api.note.Note;
042    import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
043    import org.kuali.rice.kim.api.identity.principal.Principal;
044    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
045    import org.kuali.rice.krad.service.KRADServiceLocator;
046    
047    import java.math.BigDecimal;
048    import java.util.Date;
049    import java.util.List;
050    
051    public class LeaveCalendarServiceImpl implements LeaveCalendarService {
052            
053            private static final Logger LOG = Logger.getLogger(LeaveCalendarServiceImpl.class);
054    
055        private LeaveCalendarDao leaveCalendarDao;
056    
057        @Override
058        public LeaveCalendarDocument getLeaveCalendarDocument(String documentId) {
059            LeaveCalendarDocument lcd = null;
060            LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(documentId);
061    
062            if (lcdh != null) {
063                lcd = new LeaveCalendarDocument(lcdh);
064                CalendarEntries pce = TkServiceLocator.getCalendarService().getCalendarDatesByPayEndDate(lcdh.getPrincipalId(), lcdh.getEndDate(), LMConstants.LEAVE_CALENDAR_TYPE);
065                lcd.setCalendarEntry(pce);
066            } else {
067                throw new RuntimeException("Could not find LeaveCalendarDocumentHeader for DocumentID: " + documentId);
068            }
069    
070            List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(documentId);
071            lcd.setLeaveBlocks(leaveBlocks);
072    
073            // Fetching assignments
074            List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(lcdh.getPrincipalId(), lcd.getCalendarEntry());
075            lcd.setAssignments(assignments);
076            
077            return lcd;
078        }
079    
080        @Override
081        public LeaveCalendarDocument openLeaveCalendarDocument(String principalId, CalendarEntries calEntry) throws WorkflowException {
082            LeaveCalendarDocument doc;
083    
084            Date begin = calEntry.getBeginPeriodDateTime();
085            Date end = calEntry.getEndPeriodDateTime();
086    
087            LeaveCalendarDocumentHeader header = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, begin, end);
088            if (header == null) {
089                EntityNamePrincipalName person = KimApiServiceLocator.getIdentityService().getDefaultNamesForPrincipalId(principalId);
090                String principalName = person != null && person.getDefaultName() != null ? person.getDefaultName().getCompositeName() : StringUtils.EMPTY;
091                String beginDateString = TKUtils.formatDate(new java.sql.Date(begin.getTime()));
092                String endDateString = TKUtils.formatDate(new java.sql.Date(end.getTime()));
093                String leaveCalendarDocumentTitle = LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE + " - " + principalName + " (" + principalId + ") - " + beginDateString + "-" + endDateString;
094                
095                doc = initiateWorkflowDocument(principalId, begin, end, calEntry, LeaveCalendarDocument.LEAVE_CALENDAR_DOCUMENT_TYPE, leaveCalendarDocumentTitle);
096            } else {
097                doc = getLeaveCalendarDocument(header.getDocumentId());
098            }
099            doc.setCalendarEntry(calEntry);
100            // TODO: need to set the summary
101            return doc;
102        }
103        
104        //Should only create leave calendar document if active jobs were found with flsa elig = no and ben elg = yes
105        public boolean shouldCreateLeaveDocument(String principalId, CalendarEntries calEntry){
106            if (StringUtils.isEmpty(principalId) || calEntry == null) {
107                return false;
108            }
109            
110            boolean isPlanningCalendar = TkServiceLocator.getLeaveCalendarService().isLeavePlanningCalendar(principalId, calEntry.getBeginPeriodDateTime(), calEntry.getEndPeriodDateTime());
111            if (isPlanningCalendar) {
112                    return false;
113            }
114            
115            List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByPayEntry(principalId, calEntry);
116            List<Assignment> results = TkServiceLocator.getAssignmentService().filterAssignments(assignments, TkConstants.FLSA_STATUS_EXEMPT, true);
117            return CollectionUtils.isNotEmpty(results);
118        }
119        
120        protected LeaveCalendarDocument initiateWorkflowDocument(String principalId, Date payBeginDate, Date payEndDate, CalendarEntries calendarEntries, String documentType, String title) throws WorkflowException {
121            LeaveCalendarDocument leaveCalendarDocument = null;
122            WorkflowDocument workflowDocument = null;
123    
124            workflowDocument =  WorkflowDocumentFactory.createDocument(principalId, documentType, title);
125    
126            String status = workflowDocument.getStatus().getCode();
127            LeaveCalendarDocumentHeader documentHeader = new LeaveCalendarDocumentHeader(workflowDocument.getDocumentId(), principalId, payBeginDate, payEndDate, status);
128    
129            documentHeader.setDocumentId(workflowDocument.getDocumentId());
130            documentHeader.setDocumentStatus(TkConstants.ROUTE_STATUS.INITIATED);
131    
132            KRADServiceLocator.getBusinessObjectService().save(documentHeader);
133            
134            leaveCalendarDocument = new LeaveCalendarDocument(documentHeader);
135            leaveCalendarDocument.setCalendarEntry(calendarEntries);
136            loadLeaveCalendarDocumentData(leaveCalendarDocument, principalId, calendarEntries);
137            TkServiceLocator.getTkSearchableAttributeService().updateSearchableAttribute(leaveCalendarDocument, payEndDate);
138            
139            updateLeaveBlockDocumentIds(principalId, payBeginDate, payEndDate, workflowDocument.getDocumentId());
140            
141            updatePlannedLeaveBlocks(principalId, payBeginDate, payEndDate);
142    
143            return leaveCalendarDocument;
144        }
145        
146        private void updateLeaveBlockDocumentIds(String principalId, Date beginDate, Date endDate, String documentId) {
147            List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
148            
149            for (LeaveBlock leaveBlock : leaveBlocks) {
150                    leaveBlock.setDocumentId(documentId);
151            }
152            
153            TkServiceLocator.getLeaveBlockService().saveLeaveBlocks(leaveBlocks);
154        }
155        
156        private void updatePlannedLeaveBlocks(String principalId, Date beginDate, Date endDate) {
157            String batchUserPrincipalId = getBatchUserPrincipalId();
158            
159            if (batchUserPrincipalId != null) {
160                    List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
161            
162                    for (LeaveBlock leaveBlock : leaveBlocks) {
163                            if (StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.PLANNED) 
164                                            || StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.DEFERRED)) {
165                                    TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
166                            } else if (StringUtils.equals(leaveBlock.getRequestStatus(), LMConstants.REQUEST_STATUS.REQUESTED)) {
167                            if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.DELETE)) {
168                                    TkServiceLocator.getLeaveBlockService().deleteLeaveBlock(leaveBlock.getLmLeaveBlockId(), batchUserPrincipalId);
169                            } else if (StringUtils.equals(getInitiateLeaveRequestAction(), LMConstants.INITIATE_LEAVE_REQUEST_ACTION_OPTIONS.APPROVE)) {
170                                    List<LeaveRequestDocument> leaveRequestDocuments = TkServiceLocator.getLeaveRequestDocumentService().getLeaveRequestDocumentsByLeaveBlockId(leaveBlock.getLmLeaveBlockId());
171                                    for (LeaveRequestDocument leaveRequestDocument : leaveRequestDocuments) {
172                                            TkServiceLocator.getLeaveRequestDocumentService().suBlanketApproveLeave(leaveRequestDocument.getDocumentNumber(), batchUserPrincipalId);
173                                    }
174                            }
175                            }
176                    }
177            } else {
178                    String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
179                    LOG.error("Could not update leave request blocks due to missing batch user " + principalName);
180            }
181        }
182        
183        private String getBatchUserPrincipalId() {
184            String principalName = ConfigContext.getCurrentContextConfig().getProperty(TkConstants.BATCH_USER_PRINCIPAL_NAME);
185            Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(principalName);
186            return principal == null ? null : principal.getPrincipalId();
187        }
188        
189        private String getInitiateLeaveRequestAction() {
190            return ConfigContext.getCurrentContextConfig().getProperty(LMConstants.INITIATE_LEAVE_REQUEST_ACTION);
191        }
192    
193        /**
194         * Preload the document data. It preloads:
195         * - LeaveBlocks on the document.
196         * @param ldoc
197         * @param principalId
198         * @param calEntry
199         */
200        protected void loadLeaveCalendarDocumentData(LeaveCalendarDocument ldoc, String principalId, CalendarEntries calEntry) {
201            List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksForDocumentId(ldoc.getDocumentId());
202            ldoc.setLeaveBlocks(leaveBlocks);
203            List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calEntry);
204            ldoc.setAssignments(assignments);
205        }
206    
207        public LeaveCalendarDao getLeaveCalendarDao() {
208            return leaveCalendarDao;
209        }
210    
211        public void setLeaveCalendarDao(LeaveCalendarDao leaveCalendarDao) {
212            this.leaveCalendarDao = leaveCalendarDao;
213        }
214    
215            @Override
216            public LeaveCalendarDocument getLeaveCalendarDocument(
217                            String principalId, CalendarEntries calendarEntry) {
218                    LeaveCalendarDocument leaveCalendarDocument = new LeaveCalendarDocument(calendarEntry);
219                    LeaveCalendarDocumentHeader lcdh = new LeaveCalendarDocumentHeader();
220                    lcdh.setBeginDate(calendarEntry.getBeginPeriodDateTime());
221                    lcdh.setEndDate(calendarEntry.getEndPeriodDateTime());
222                    leaveCalendarDocument.setDocumentHeader(lcdh);
223                    // Fetching assignments
224            List<Assignment> assignments = TkServiceLocator.getAssignmentService().getAssignmentsByCalEntryForLeaveCalendar(principalId, calendarEntry);
225            leaveCalendarDocument.setAssignments(assignments);
226                    return leaveCalendarDocument;
227            }
228    
229        @Override
230        public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
231            leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.ROUTE, principalId, leaveCalendarDocument);
232        }
233        
234        @Override
235        public void routeLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) {
236            leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument);
237        }
238    
239        @Override
240        public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
241            leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.APPROVE, principalId, leaveCalendarDocument);
242        }
243        
244        @Override
245        public void approveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument, String action) {
246            leaveCalendarDocumentAction(action, principalId, leaveCalendarDocument);
247        }
248    
249        @Override
250        public void disapproveLeaveCalendar(String principalId, LeaveCalendarDocument leaveCalendarDocument) {
251            leaveCalendarDocumentAction(TkConstants.DOCUMENT_ACTIONS.DISAPPROVE, principalId, leaveCalendarDocument);
252        }
253    
254        public boolean isReadyToApprove(LeaveCalendarDocument document) {
255            if (document == null) {
256                return false;
257            }
258            List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
259                            document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER);
260            leaveBlocks.addAll(TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(document.getPrincipalId(),
261                            document.getCalendarEntry().getBeginPeriodDate(), document.getCalendarEntry().getEndPeriodDate(), LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT));
262            for(LeaveBlock lb : leaveBlocks) {
263                    if(!StringUtils.equals(lb.getRequestStatus(),LMConstants.REQUEST_STATUS.APPROVED) &&
264                                    !StringUtils.equals(lb.getRequestStatus(), LMConstants.REQUEST_STATUS.DISAPPROVED))
265                            return false;
266            }
267            // check if there are any pending calendars are there
268            LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getMinBeginDatePendingLeaveCalendar(document.getPrincipalId());
269            if (lcdh != null){             //if there were any pending document
270                //check to see if it's before the current document. if it is, then this document is not approvable.
271                if (TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(document.getDocumentId()).getBeginDate().compareTo(lcdh.getEndDate()) >= 0){
272                    return false;
273                }
274            }
275    
276            return true;
277    /*        List<BalanceTransfer> balanceTransfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(document.getPrincipalId(),
278                    document.getCalendarEntry().getBeginPeriodDate(),
279                    document.getCalendarEntry().getEndPeriodDate());
280            if (!CollectionUtils.isEmpty(balanceTransfers))   {
281                    for(BalanceTransfer balanceTransfer : balanceTransfers) {
282                            if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(balanceTransfer.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
283                                    return false;
284                        if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, balanceTransfer.getStatus())
285                                && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, balanceTransfer.getStatus())) {
286                            return false;
287                        }
288                    }
289            }
290            List<LeavePayout> leavePayouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(document.getPrincipalId(),
291                            document.getCalendarEntry().getBeginPeriodDate(),
292                            document.getCalendarEntry().getEndPeriodDate());
293            if (!CollectionUtils.isEmpty(leavePayouts)) {
294                    for(LeavePayout payout : leavePayouts) {
295                            if(StringUtils.equals(TkConstants.DOCUMENT_STATUS.get(payout.getStatus()), TkConstants.ROUTE_STATUS.ENROUTE))
296                                    return false;
297                        if (!StringUtils.equals(LMConstants.REQUEST_STATUS.APPROVED, payout.getStatus())
298                                && !StringUtils.equals(LMConstants.REQUEST_STATUS.DISAPPROVED, payout.getStatus())) {
299                            return false;
300                        }
301                    }
302            }
303            return true;*/
304        }
305    
306        protected void leaveCalendarDocumentAction(String action, String principalId, LeaveCalendarDocument leaveCalendarDocument) {
307            WorkflowDocument wd = null;
308            if (leaveCalendarDocument != null) {
309                String rhid = leaveCalendarDocument.getDocumentId();
310                wd = WorkflowDocumentFactory.loadDocument(principalId, rhid);
311    
312                if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.ROUTE)) {
313                    wd.route("Routing for Approval");
314                } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_ROUTE)) {
315                    Note.Builder builder = Note.Builder.create(rhid, principalId);
316                    builder.setCreateDate(new DateTime());
317                    builder.setText("Routed via Employee Approval batch job");
318                    KewApiServiceLocator.getNoteService().createNote(builder.build());
319                    
320                    wd.route("Batch job routing leave calendar");
321                } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.APPROVE)) {
322                    if (TKUser.getCurrentTargetRoles().isSystemAdmin() &&
323                            !TKUser.getCurrentTargetRoles().isApproverForTimesheet(leaveCalendarDocument)) {
324                        wd.superUserBlanketApprove("Superuser approving timesheet.");
325                    } else {
326                        wd.approve("Approving timesheet.");
327                    }
328                } else if (StringUtils.equals(action, TkConstants.BATCH_JOB_ACTIONS.BATCH_JOB_APPROVE)) {
329                     Note.Builder builder = Note.Builder.create(rhid, principalId);
330                     builder.setCreateDate(new DateTime());
331                     builder.setText("Approved via Supervisor Approval batch job");
332                     KewApiServiceLocator.getNoteService().createNote(builder.build());
333                    
334                    wd.superUserBlanketApprove("Batch job approving leave calendar");
335                } else if (StringUtils.equals(action, TkConstants.DOCUMENT_ACTIONS.DISAPPROVE)) {
336                    if (TKUser.getCurrentTargetRoles().isSystemAdmin()
337                            && !TKUser.getCurrentTargetRoles().isApproverForTimesheet(leaveCalendarDocument)) {
338                        wd.superUserDisapprove("Superuser disapproving timesheet.");
339                    } else {
340                        wd.disapprove("Disapproving timesheet.");
341                    }
342                }
343            }
344        }
345    
346        public boolean isLeavePlanningCalendar(String principalId, Date beginDate, Date endDate) {
347            Date today = new Date();
348    
349            List<Job> jobs = TkServiceLocator.getJobService().getJobs(principalId, endDate);
350            for (Job job : jobs) {
351                //  Check for Leave eligibility.
352                if (job.isEligibleForLeave()) {
353                    //  Check for Time (FLSA nonexempt) jobs. If one exists, then the Leave Calendar is always a Leave Planning Calendar
354                    if (job.getFlsaStatus().equalsIgnoreCase(TkConstants.FLSA_STATUS_NON_EXEMPT)) {
355                        return true;
356                    } else {
357                        //  If leave eligible and FLSA exempt, then report leave in the Leave Calendar. Use the date to determine Planning vs Recording Calendars.
358                        if ( beginDate.after(today) ) {
359                            //  future period, this is a Planning Calendar.
360                            return true;
361                        } else {
362                            //  not a future period, this is a Reporting Calendar.
363                            return false;
364                        }
365                    }
366                } else {
367                //  not leave eligible
368                    return false;
369                }
370            }
371            return false;
372        }
373    
374    }
375