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