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