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 }