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 }