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