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.leave.approval.service;
017    
018    import java.math.BigDecimal;
019    import java.text.SimpleDateFormat;
020    import java.util.ArrayList;
021    import java.util.Date;
022    import java.util.HashMap;
023    import java.util.HashSet;
024    import java.util.LinkedHashMap;
025    import java.util.LinkedList;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Map.Entry;
029    import java.util.Set;
030    
031    import org.apache.commons.collections.CollectionUtils;
032    import org.apache.commons.lang3.StringUtils;
033    import org.apache.commons.lang3.time.DateUtils;
034    import org.joda.time.DateTime;
035    import org.kuali.hr.lm.LMConstants;
036    import org.kuali.hr.lm.accrual.AccrualCategory;
037    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
038    import org.kuali.hr.lm.balancetransfer.BalanceTransfer;
039    import org.kuali.hr.lm.leaveSummary.LeaveSummary;
040    import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow;
041    import org.kuali.hr.lm.leaveblock.LeaveBlock;
042    import org.kuali.hr.lm.leavecalendar.validation.LeaveCalendarValidationUtil;
043    import org.kuali.hr.lm.leavepayout.LeavePayout;
044    import org.kuali.hr.lm.workflow.LeaveCalendarDocumentHeader;
045    import org.kuali.hr.time.approval.web.ApprovalLeaveSummaryRow;
046    import org.kuali.hr.time.assignment.Assignment;
047    import org.kuali.hr.time.calendar.CalendarEntries;
048    import org.kuali.hr.time.person.TKPerson;
049    import org.kuali.hr.time.principal.PrincipalHRAttributes;
050    import org.kuali.hr.time.principal.dao.PrincipalHRAttributesDao;
051    import org.kuali.hr.time.roles.TkUserRoles;
052    import org.kuali.hr.time.service.base.TkServiceLocator;
053    import org.kuali.hr.time.util.TKContext;
054    import org.kuali.hr.time.util.TKUser;
055    import org.kuali.hr.time.util.TKUtils;
056    import org.kuali.hr.time.util.TkConstants;
057    import org.kuali.rice.kew.api.KewApiServiceLocator;
058    import org.kuali.rice.kew.api.note.Note;
059    import org.kuali.rice.krad.util.GlobalVariables;
060    import org.springframework.jdbc.support.rowset.SqlRowSet;
061    
062    public class LeaveApprovalServiceImpl implements LeaveApprovalService{
063            public static final int DAYS_WINDOW_DELTA = 31;
064        private PrincipalHRAttributesDao principalHRAttributesDao;
065    
066        public void setPrincipalHRAttributesDao(PrincipalHRAttributesDao principalHRAttributesDao) {
067            this.principalHRAttributesDao = principalHRAttributesDao;
068        }
069            
070            @Override
071            public List<ApprovalLeaveSummaryRow> getLeaveApprovalSummaryRows(List<TKPerson> persons, CalendarEntries payCalendarEntries, List<Date> leaveSummaryDates) {
072                    Date payBeginDate = payCalendarEntries.getBeginPeriodDate();
073                    Date payEndDate = payCalendarEntries.getEndPeriodDate();
074                    List<ApprovalLeaveSummaryRow> rowList = new ArrayList<ApprovalLeaveSummaryRow>();           
075                    
076                    for(TKPerson aPerson : persons) {
077                            String principalId = aPerson.getPrincipalId();
078                            ApprovalLeaveSummaryRow aRow = new ApprovalLeaveSummaryRow();
079                List<Note> notes = new ArrayList<Note>();
080    //            List<String> warnings = new ArrayList<String>();
081                            aRow.setName(aPerson.getPrincipalName());
082                            aRow.setPrincipalId(aPerson.getPrincipalId());
083                            
084                            String lastApprovedString = "No previous approved leave calendar information";
085                            LeaveCalendarDocumentHeader lastApprovedDoc = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getMaxEndDateApprovedLeaveCalendar(principalId);
086                            if(lastApprovedDoc != null) {
087                                    lastApprovedString = "Last Approved: " + (new SimpleDateFormat("MMM yyyy")).format(lastApprovedDoc.getBeginDate());
088                }
089                            aRow.setLastApproveMessage(lastApprovedString);
090                            
091                            LeaveCalendarDocumentHeader aDoc = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
092                            if(aDoc != null) {
093                                    aRow.setDocumentId(aDoc.getDocumentId());
094                                    aRow.setApprovalStatus(TkConstants.DOC_ROUTE_STATUS.get(aDoc.getDocumentStatus()));
095                    notes = getNotesForDocument(aDoc.getDocumentId());
096                            }
097                            List<LeaveCalendarDocumentHeader> docList = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getApprovalDelinquentDocumentHeaders(principalId);
098                            if(docList.size() > LMConstants.DELINQUENT_LEAVE_CALENDARS_LIMIT ) {
099                                    aRow.setMoreThanOneCalendar(true);
100                            }
101                            
102                            List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, payBeginDate, payEndDate);
103    
104                aRow.setLeaveBlockList(leaveBlocks);
105                            Map<Date, Map<String, BigDecimal>> earnCodeLeaveHours = getEarnCodeLeaveHours(leaveBlocks, leaveSummaryDates);
106                            aRow.setEarnCodeLeaveHours(earnCodeLeaveHours);
107                aRow.setNotes(notes);
108    
109                Map<String, Set<String>> allMessages = findWarnings(principalId, payCalendarEntries, leaveBlocks);
110                            
111                            Map<String,Set<String>> transactionalMessages = findTransactionsWithinPeriod(aDoc, payCalendarEntries);
112                            
113                            allMessages.get("infoMessages").addAll(transactionalMessages.get("infoMessages"));
114                            allMessages.get("warningMessages").addAll(transactionalMessages.get("warningMessages"));
115                            allMessages.get("actionMessages").addAll(transactionalMessages.get("actionMessages"));
116    
117                List<String> warningMessages = new ArrayList<String>();
118                warningMessages.addAll(allMessages.get("warningMessages"));
119                warningMessages.addAll(allMessages.get("infoMessages"));
120                warningMessages.addAll(allMessages.get("actionMessages"));
121    
122                aRow.setWarnings(warningMessages); //these are only warning messages.
123                            
124                            rowList.add(aRow);
125                    }
126                    
127                    return rowList;
128            }
129    
130        private Map<String,Set<String>> findTransactionsWithinPeriod(LeaveCalendarDocumentHeader aDoc,
131                            CalendarEntries payCalendarEntries) {
132                    Map<String,Set<String>> allMessages = new HashMap<String,Set<String>>();
133                    
134                    allMessages.put("actionMessages", new HashSet<String>());
135                    allMessages.put("infoMessages", new HashSet<String>());
136                    allMessages.put("warningMessages", new HashSet<String>());
137                    if(aDoc != null) {
138                            allMessages = LeaveCalendarValidationUtil.validatePendingTransactions(aDoc.getPrincipalId(), payCalendarEntries.getBeginPeriodDate(), payCalendarEntries.getEndPeriodDate());
139                    }
140                    return allMessages;
141            }
142    
143            private Map<String, Set<String>> findWarnings(String principalId, CalendarEntries calendarEntry, List<LeaveBlock> leaveBlocks) {
144    //        List<String> warnings = LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
145            Map<String, Set<String>> allMessages= LeaveCalendarValidationUtil.getWarningMessagesForLeaveBlocks(leaveBlocks);
146            //get LeaveSummary and check for warnings
147            Map<String, Set<LeaveBlock>> eligibilities;
148            try {
149                    eligibilities = TkServiceLocator.getAccrualCategoryMaxBalanceService().getMaxBalanceViolations(calendarEntry, principalId);
150            } catch (Exception e) {
151                    eligibilities = null;
152            }
153            if (eligibilities != null) {
154                    for (Entry<String,Set<LeaveBlock>> entry : eligibilities.entrySet()) {
155                            for(LeaveBlock block : entry.getValue()) {
156    
157                                    AccrualCategoryRule rule = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRule(block.getAccrualCategoryRuleId());
158                                    if (rule != null) {
159                                            AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(rule.getLmAccrualCategoryId());
160                                            if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.TRANSFER)) {
161                                                    //Todo: add link to balance transfer
162                                                    allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");   //warningMessages
163                                            } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.LOSE)) {
164                                                    //Todo: compute and display amount of time lost.
165                                                    allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");      //warningMessages
166                                            } else if (rule.getActionAtMaxBalance().equals(LMConstants.ACTION_AT_MAX_BAL.PAYOUT)){
167                                                    //Todo: display information about the payout
168                                                    allMessages.get("warningMessages").add("Accrual Category '" + accrualCategory.getAccrualCategory() + "' is over max balance.");      //warningMessages
169    
170                                            }
171    
172                                    }
173                            }
174                    }
175            }    
176            return allMessages;
177        }
178            
179            @Override
180            public Map<Date, Map<String, BigDecimal>> getEarnCodeLeaveHours(List<LeaveBlock> leaveBlocks, List<Date> leaveSummaryDates) {
181                    Map<Date, Map<String, BigDecimal>> earnCodeLeaveHours = new LinkedHashMap<Date, Map<String, BigDecimal>>();
182                    
183                    for (Date leaveSummaryDate : leaveSummaryDates) {
184                            earnCodeLeaveHours.put(leaveSummaryDate, new LinkedHashMap<String, BigDecimal>());
185                    }
186                    
187                    for (LeaveBlock lb : leaveBlocks) {
188                            DateTime leaveDate = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay();
189                            
190                            if (earnCodeLeaveHours.get(leaveDate.toDate()) != null) {
191                                    Map<String, BigDecimal> leaveHours = earnCodeLeaveHours.get(leaveDate.toDate());
192    
193                                    BigDecimal amount = lb.getLeaveAmount();
194                    String key = lb.getEarnCode() + "|" + lb.getRequestStatus() + "|" + lb.getLeaveBlockType();
195                                    if (leaveHours.get(key) != null) {
196                                            amount = leaveHours.get(key).add(lb.getLeaveAmount());
197                                    }
198                                    
199                                    leaveHours.put(key, amount);
200                            }
201                    }
202                    
203                    return earnCodeLeaveHours;
204            }
205    
206            public Map<String, LeaveCalendarDocumentHeader> getLeaveDocumentHeaderMap(List<TKPerson> persons, Date payBeginDate, Date payEndDate) {
207                    Map<String, LeaveCalendarDocumentHeader> leaveDocumentHeaderMap = new LinkedHashMap<String, LeaveCalendarDocumentHeader>();
208                    if (CollectionUtils.isNotEmpty(persons)) {
209                            for (TKPerson person : persons) {
210                                    String principalId = person.getPrincipalId();
211                                    LeaveCalendarDocumentHeader aHeader = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
212                                    if(aHeader != null) {
213                                            leaveDocumentHeaderMap.put(principalId, aHeader);
214                                    }
215                                    
216                            }
217                    }
218                    return leaveDocumentHeaderMap;
219            }
220            
221            @Override
222            public List<Map<String, Object>> getLeaveApprovalDetailSections(LeaveCalendarDocumentHeader lcdh)  {
223                    
224                    List<Map<String, Object>> acRows = new ArrayList<Map<String, Object>>();
225                    
226                    String principalId = lcdh.getPrincipalId();
227            CalendarEntries calendarEntry = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(lcdh.getDocumentId()).getCalendarEntry();
228                    //CalendarEntries calendarEntry = TkServiceLocator.getCalendarEntriesService().getCalendarEntriesByBeginAndEndDate(lcdh.getBeginDate(), lcdh.getEndDate());
229                    if(calendarEntry != null) {
230                            Date beginDate = calendarEntry.getBeginPeriodDate();
231                            Date endDate = calendarEntry.getEndPeriodDate();
232                            LeaveSummary leaveSummary;
233                            List<Date> leaveSummaryDates = TkServiceLocator.getLeaveSummaryService().getLeaveSummaryDates(calendarEntry);
234                try {
235                    leaveSummary = TkServiceLocator.getLeaveSummaryService().getLeaveSummary(principalId, calendarEntry);
236                } catch (Exception e) {
237                    leaveSummary = null;
238                }
239                List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocks(principalId, beginDate, endDate);
240                            Map<Date, Map<String, BigDecimal>> accrualCategoryLeaveHours = getAccrualCategoryLeaveHours(leaveBlocks, leaveSummaryDates);
241    
242                            //get all accrual categories of this employee
243                            PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, endDate);
244                            if(pha != null) {
245                                    List<AccrualCategory> acList = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), new java.sql.Date(endDate.getTime()));
246                                    for(AccrualCategory ac : acList) {
247                                            List<BigDecimal> acDayDetails = new ArrayList<BigDecimal>();
248                                            Map<String, Object> displayMap = new HashMap<String, Object>();
249                                            BigDecimal totalAmount = BigDecimal.ZERO;
250                                            displayMap.put("accrualCategory", ac.getAccrualCategory());
251                                            int index = 0;
252                                            for (Date leaveSummaryDate : leaveSummaryDates) {
253                                                    acDayDetails.add(index, null);
254                                                    if (accrualCategoryLeaveHours.get(leaveSummaryDate) != null) {
255                                                            Map<String, BigDecimal> leaveHours = accrualCategoryLeaveHours.get(leaveSummaryDate);
256                                                            if (leaveHours.containsKey(ac.getAccrualCategory())) {
257                                                                    BigDecimal amount =  leaveHours.get(ac.getAccrualCategory());
258                                                                    totalAmount = totalAmount.add(amount);
259                                                                    acDayDetails.set(index, amount);
260                                                            }
261                                                    }
262                                                    index++;
263                                            }
264                        LeaveSummaryRow lsr = leaveSummary == null ? null : leaveSummary.getLeaveSummaryRowForAccrualCtgy(ac.getAccrualCategory());
265                                            displayMap.put("periodUsage", totalAmount);
266                                            displayMap.put("availableBalance", BigDecimal.ZERO);
267                        displayMap.put("availableBalance", lsr == null ? BigDecimal.ZERO : lsr.getLeaveBalance());
268                                            displayMap.put("daysDetail", acDayDetails);
269                                            displayMap.put("daysSize", acDayDetails.size());
270                                            acRows.add(displayMap);
271                                    }
272                            }
273                            
274                    }
275                    return acRows;
276            }
277    
278        @Override
279        public List<Note> getNotesForDocument(String documentNumber) {
280            return KewApiServiceLocator.getNoteService().getNotes(documentNumber);
281        }
282    
283            @Override
284            public Map<Date, Map<String, BigDecimal>> getAccrualCategoryLeaveHours(List<LeaveBlock> leaveBlocks, List<Date> leaveSummaryDates) {
285                    Map<Date, Map<String, BigDecimal>> accrualCategoryLeaveHours = new LinkedHashMap<Date, Map<String, BigDecimal>>();
286                    
287                    for (Date leaveSummaryDate : leaveSummaryDates) {
288                            accrualCategoryLeaveHours.put(leaveSummaryDate, new LinkedHashMap<String, BigDecimal>());
289                    }
290                    
291                    for (LeaveBlock lb : leaveBlocks) {
292                            DateTime leaveDate = new DateTime(lb.getLeaveDate()).toLocalDate().toDateTimeAtStartOfDay();
293                            
294                            AccrualCategory ac = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(lb.getAccrualCategory(), lb.getLeaveDate());
295                            if (ac != null && ac.getShowOnGrid().equals("Y")) {
296                                    if (accrualCategoryLeaveHours.get(leaveDate.toDate()) != null) {
297                                            Map<String, BigDecimal> leaveHours = accrualCategoryLeaveHours.get(leaveDate.toDate());
298                                            
299                                            BigDecimal amount = lb.getLeaveAmount();
300                                            if (leaveHours.get(ac.getAccrualCategory()) != null) {
301                                                    amount = leaveHours.get(ac.getAccrualCategory()).add(lb.getLeaveAmount());
302                                            }
303                                            
304                                            leaveHours.put(ac.getAccrualCategory(), amount);
305                                    }
306                            }
307                    }
308                    
309                    return accrualCategoryLeaveHours;
310            }
311    
312        @Override
313        public List<String> getUniqueLeavePayGroupsForPrincipalIds(List<String> principalIds) {
314            return principalHRAttributesDao.getUniqueLeavePayGroupsForPrincipalIds(principalIds);
315        }
316            
317            @Override
318            public List<CalendarEntries> getAllLeavePayCalendarEntriesForApprover(String principalId, Date currentDate) {
319                    Set<String> principals = new HashSet<String>();
320                    Set<Long> approverWorkAreas = TkUserRoles.getUserRoles(GlobalVariables.getUserSession().getPrincipalId()).getApproverWorkAreas();
321    
322                    // Get all of the principals within our window of time.
323                    for (Long waNum : approverWorkAreas) {
324                            List<Assignment> assignments = TkServiceLocator
325                                            .getAssignmentService().getActiveAssignmentsForWorkArea(waNum, TKUtils.getTimelessDate(currentDate));
326    
327                            if (assignments != null) {
328                                    for (Assignment assignment : assignments) {
329                                            principals.add(assignment.getPrincipalId());
330                                    }
331                            }
332                    }
333                    List<LeaveCalendarDocumentHeader> documentHeaders = new ArrayList<LeaveCalendarDocumentHeader>();
334                    for(String pid : principals) {
335                            documentHeaders.addAll(TkServiceLocator.getLeaveCalendarDocumentHeaderService().getAllDocumentHeadersForPricipalId(pid));
336                    }
337                    Set<CalendarEntries> payPeriodSet = new HashSet<CalendarEntries>();
338                    for(LeaveCalendarDocumentHeader lcdh : documentHeaders) {
339                CalendarEntries pe = TkServiceLocator.getLeaveCalendarService().getLeaveCalendarDocument(lcdh.getDocumentId()).getCalendarEntry();
340                    if(pe != null) {
341                            payPeriodSet.add(pe);
342                    }
343            }
344                    List<CalendarEntries> ppList = new ArrayList<CalendarEntries>(payPeriodSet);
345            
346                    return ppList;
347            }
348            @Override
349            public void removeNonLeaveEmployees(List<String> principalIds) {
350                    if(CollectionUtils.isNotEmpty(principalIds)) {
351                            java.sql.Date asOfDate = TKUtils.getTimelessDate(null);
352                            List<String> idList = new ArrayList<String>();
353                            idList.addAll(principalIds);
354                    for(String principalId: idList) {
355                            boolean leaveFlag = false;
356                            List<Assignment> activeAssignments = TkServiceLocator.getAssignmentService().getAssignments(principalId, asOfDate);
357                            if(CollectionUtils.isNotEmpty(activeAssignments)) {
358                                    for(Assignment assignment : activeAssignments) {
359                                            if(assignment != null && assignment.getJob() != null && assignment.getJob().isEligibleForLeave()) {
360                                                    leaveFlag = true;
361                                                    break;
362                                            }
363                                    }
364                                    if(!leaveFlag) {  // employee is not eligible for leave, remove the id from principalIds
365                                            principalIds.remove(principalId);
366                                    }
367                            }
368                    }
369                    }
370            }
371            
372            @Override
373            public List<String> getLeavePrincipalIdsWithSearchCriteria(List<String> workAreaList, String calendarGroup, java.sql.Date effdt, java.sql.Date beginDate, java.sql.Date endDate) {
374                    if (CollectionUtils.isEmpty(workAreaList)) {
375                  return new ArrayList<String>();
376                }
377                    
378                    List<String> principalIds = TkServiceLocator.getAssignmentService().getPrincipalIds(workAreaList, effdt, beginDate, endDate);
379                    TkServiceLocator.getLeaveApprovalService().removeNonLeaveEmployees(principalIds);
380    
381                    if(CollectionUtils.isEmpty(principalIds)) {
382                    return new ArrayList<String>();
383                    }
384                    // use unique principalIds and selected calendarGroup to get unique ids from principalHRAttributes table
385                    List<String> idList = CollectionUtils.isEmpty(principalIds) ? 
386                            new ArrayList<String> () 
387                            : TkServiceLocator.getPrincipalHRAttributeService()
388                                    .getActiveEmployeesIdForLeaveCalendarAndIdList(calendarGroup, principalIds, endDate); 
389                    
390                    return idList;
391            }       
392    
393            @Override
394            public Map<String, LeaveCalendarDocumentHeader> getPrincipalDocumehtHeader(List<TKPerson> persons, Date payBeginDate, Date payEndDate) {
395                    Map<String, LeaveCalendarDocumentHeader> principalDocumentHeader = new LinkedHashMap<String, LeaveCalendarDocumentHeader>();
396                    for (TKPerson person : persons) {
397                            String principalId = person.getPrincipalId();
398                            LeaveCalendarDocumentHeader lcdh = TkServiceLocator.getLeaveCalendarDocumentHeaderService().getDocumentHeader(principalId, payBeginDate, payEndDate);
399                            if(lcdh != null) {
400                                    principalDocumentHeader.put(principalId, lcdh); 
401                            }
402                    }
403                    return principalDocumentHeader;
404            }
405    
406            @Override
407            public boolean isActiveAssignmentFoundOnJobFlsaStatus(String principalId,
408                            String flsaStatus, boolean chkForLeaveEligible) {
409                    boolean isActiveAssFound = false;
410                    java.sql.Date asOfDate = TKUtils.getTimelessDate(null);
411                    List<Assignment> activeAssignments = TkServiceLocator
412                                    .getAssignmentService().getAssignments(principalId, asOfDate);
413                    if (activeAssignments != null && !activeAssignments.isEmpty()) {
414                            for (Assignment assignment : activeAssignments) {
415                                    if (assignment != null
416                                                    && assignment.getJob() != null
417                                                    && assignment.getJob().getFlsaStatus() != null
418                                                    && assignment.getJob().getFlsaStatus()
419                                                                    .equalsIgnoreCase(flsaStatus)) {
420                                            if (chkForLeaveEligible) {
421                                                    isActiveAssFound = assignment.getJob()
422                                                                    .isEligibleForLeave();
423                                                    if (!isActiveAssFound) {
424                                                            continue;
425                                                    }
426                                            }
427                                            isActiveAssFound = true;
428                                            break;
429                                    }
430                            }
431                    }
432                    return isActiveAssFound;
433        }
434    }