001 /** 002 * Copyright 2004-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.hr.lm.leavecalendar.validation; 017 018 import org.apache.commons.collections.CollectionUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.joda.time.DateTime; 021 import org.joda.time.DateTimeConstants; 022 import org.kuali.hr.lm.LMConstants; 023 import org.kuali.hr.lm.accrual.AccrualCategory; 024 import org.kuali.hr.lm.employeeoverride.EmployeeOverride; 025 import org.kuali.hr.lm.leave.web.LeaveCalendarWSForm; 026 import org.kuali.hr.lm.leaveSummary.LeaveSummary; 027 import org.kuali.hr.lm.leaveSummary.LeaveSummaryRow; 028 import org.kuali.hr.lm.leaveblock.LeaveBlock; 029 import org.kuali.hr.time.calendar.CalendarEntries; 030 import org.kuali.hr.time.earncode.EarnCode; 031 import org.kuali.hr.time.earncodegroup.EarnCodeGroup; 032 import org.kuali.hr.time.service.base.TkServiceLocator; 033 import org.kuali.hr.time.util.TKContext; 034 import org.kuali.hr.time.util.TKUtils; 035 import org.kuali.hr.time.util.TkConstants; 036 037 import java.math.BigDecimal; 038 import java.sql.Date; 039 import java.util.*; 040 041 import org.kuali.rice.kew.api.KewApiServiceLocator; 042 import org.kuali.rice.kew.api.document.DocumentStatus; 043 import org.kuali.rice.krad.util.ObjectUtils; 044 045 public class LeaveCalendarValidationUtil { 046 047 //begin KPME-1263 048 public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveCalendarWSForm lcf) { 049 LeaveBlock updatedLeaveBlock = null; 050 if(lcf.getLeaveBlockId() != null) { 051 updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId()); 052 } 053 return validateLeaveAccrualRuleMaxUsage(lcf.getLeaveSummary(), lcf.getSelectedEarnCode(), lcf.getStartDate(), 054 lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock); 055 } 056 057 public static List<String> validateLeaveAccrualRuleMaxUsage(LeaveSummary ls, String selectedEarnCode, String leaveStartDateString, 058 String leaveEndDateString, BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) { 059 List<String> errors = new ArrayList<String>(); 060 String principalId = TKContext.getTargetPrincipalId(); 061 long daysSpan = TKUtils.getDaysBetween(TKUtils.formatDateString(leaveStartDateString), TKUtils.formatDateString(leaveEndDateString)); 062 if(ls != null && CollectionUtils.isNotEmpty(ls.getLeaveSummaryRows())) { 063 BigDecimal oldLeaveAmount = null; 064 boolean earnCodeChanged = false; 065 if(updatedLeaveBlock != null) { 066 if(!updatedLeaveBlock.getEarnCode().equals(selectedEarnCode)) { 067 earnCodeChanged = true; 068 } 069 if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) { 070 oldLeaveAmount = updatedLeaveBlock.getLeaveAmount(); 071 } 072 } 073 Date aDate = TKUtils.formatDateString(leaveEndDateString); 074 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(selectedEarnCode, aDate); 075 if(earnCodeObj != null) { 076 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), aDate); 077 if(accrualCategory != null) { 078 List<LeaveSummaryRow> rows = ls.getLeaveSummaryRows(); 079 for(LeaveSummaryRow aRow : rows) { 080 if(aRow.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) { 081 //Does employee have overrides in place? 082 List<EmployeeOverride> employeeOverrides = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverrides(principalId,TKUtils.formatDateString(leaveEndDateString)); 083 String leavePlan = accrualCategory.getLeavePlan(); 084 BigDecimal maxUsage = aRow.getUsageLimit(); 085 for(EmployeeOverride eo : employeeOverrides) { 086 if(eo.getLeavePlan().equals(leavePlan) && eo.getAccrualCategory().equals(aRow.getAccrualCategory())) { 087 if(eo.getOverrideType().equals("MU") && eo.isActive()) { 088 if(eo.getOverrideValue()!=null && !eo.getOverrideValue().equals("")) 089 maxUsage = new BigDecimal(eo.getOverrideValue()); 090 else // no limit flag 091 maxUsage = null; 092 } 093 } 094 } 095 BigDecimal ytdUsage = aRow.getYtdApprovedUsage(); 096 BigDecimal pendingLeaveBalance = aRow.getPendingLeaveRequests(); 097 BigDecimal desiredUsage = new BigDecimal(0); 098 if(pendingLeaveBalance!=null) { 099 if(oldLeaveAmount!=null) { 100 101 if(!earnCodeChanged || 102 updatedLeaveBlock.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) { 103 pendingLeaveBalance = pendingLeaveBalance.subtract(oldLeaveAmount.abs()); 104 } 105 } 106 107 desiredUsage = desiredUsage.add(pendingLeaveBalance); 108 } 109 110 desiredUsage = desiredUsage.add(leaveAmount.multiply(new BigDecimal(daysSpan+1))); 111 112 if(ytdUsage!=null) { 113 desiredUsage = desiredUsage.add(ytdUsage); 114 } 115 if(maxUsage!=null) { 116 if(desiredUsage.compareTo(maxUsage) > 0 ) { 117 errors.add("This leave request would exceed the usage limit for " + aRow.getAccrualCategory()); //errorMessages 118 } 119 } 120 } 121 } 122 } 123 } 124 } 125 return errors; 126 } 127 //End KPME-1263 128 129 //TODO: Move to WarningService 130 public static Map<String, Set<String>> validatePendingTransactions(String principalId, Date fromDate, Date toDate) { 131 Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>(); 132 133 Set<String> actionMessages = new HashSet<String>(); 134 Set<String> infoMessages = new HashSet<String>(); 135 Set<String> warningMessages = new HashSet<String>(); 136 137 allMessages.put("actionMessages", actionMessages); 138 allMessages.put("infoMessages", infoMessages); 139 allMessages.put("warningMessages", warningMessages); 140 List<LeaveBlock> leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(principalId, fromDate, toDate, LMConstants.LEAVE_BLOCK_TYPE.BALANCE_TRANSFER); 141 Set<String> workflowDocIds = new HashSet<String>(); 142 for(LeaveBlock lb : leaveBlocks) { 143 if(lb.getTransactionalDocId() != null) 144 workflowDocIds.add(lb.getTransactionalDocId()); 145 else 146 if(lb.getDescription().contains("Forfeited balance transfer amount")) 147 allMessages.get("infoMessages").add("A max balance action that forfeited accrued leave occurred on this calendar"); 148 } 149 for(String workflowDocId : workflowDocIds) { 150 DocumentStatus status = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(workflowDocId); 151 152 if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.FINAL)) { 153 allMessages.get("infoMessages").add("A transfer action occurred on this calendar"); 154 } 155 else if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.ENROUTE)) { 156 allMessages.get("actionMessages").add("A pending balance transfer exists on this calendar. It must be finalized before this calendar can be approved"); 157 } 158 else 159 allMessages.get("warningMessages").add("A balance transfer document exists for this calendar with status neither final or enroute"); 160 } 161 162 leaveBlocks = TkServiceLocator.getLeaveBlockService().getLeaveBlocksWithType(principalId, fromDate, toDate, LMConstants.LEAVE_BLOCK_TYPE.LEAVE_PAYOUT); 163 workflowDocIds = new HashSet<String>(); 164 for(LeaveBlock lb : leaveBlocks) { 165 if(lb.getTransactionalDocId() != null) 166 workflowDocIds.add(lb.getTransactionalDocId()); 167 } 168 for(String workflowDocId : workflowDocIds) { 169 DocumentStatus status = KewApiServiceLocator.getWorkflowDocumentService().getDocumentStatus(workflowDocId); 170 171 if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.FINAL)) { 172 allMessages.get("infoMessages").add("A payout action occurred on this calendar"); 173 } 174 else if(StringUtils.equals(status.getCode(), TkConstants.ROUTE_STATUS.ENROUTE)) { 175 allMessages.get("actionMessages").add("A pending payout exists on this calendar. It must be finalized before this calendar can be approved"); 176 } 177 else 178 allMessages.get("warningMessages").add("A payout document exists for this calendar with status neither final or enroute"); 179 } 180 /* //TODO: Re-combine balance transfer and leave payout... 181 List<BalanceTransfer> transfers = TkServiceLocator.getBalanceTransferService().getBalanceTransfers(principalId, fromDate, toDate); 182 for(BalanceTransfer transfer : transfers) { 183 if(StringUtils.equals(transfer.getStatus(), TkConstants.ROUTE_STATUS.ENROUTE)) { 184 allMessages.get("actionMessages").add("A pending balance transfer exists on this calendar. It must be finalized before this calendar can be approved"); //action 185 } 186 if(StringUtils.equals(transfer.getStatus() ,TkConstants.ROUTE_STATUS.FINAL)) { 187 if(StringUtils.isEmpty(transfer.getSstoId())) { 188 if(transfer.getTransferAmount().compareTo(BigDecimal.ZERO) == 0 && transfer.getAmountTransferred().compareTo(BigDecimal.ZERO) == 0) { 189 if(transfer.getForfeitedAmount() != null && transfer.getForfeitedAmount().signum() != 0) 190 allMessages.get("infoMessages").add("A transfer action that forfeited leave occured on this calendar"); //info 191 } 192 else 193 allMessages.get("infoMessages").add("A transfer action occurred on this calendar"); //info 194 } 195 else 196 allMessages.get("infoMessages").add("System scheduled time off was transferred on this calendar"); //info 197 } 198 if(StringUtils.equals(transfer.getStatus() ,TkConstants.ROUTE_STATUS.DISAPPROVED)) { 199 if(StringUtils.isEmpty(transfer.getSstoId())) { 200 if(transfer.getTransferAmount().compareTo(BigDecimal.ZERO) == 0 && transfer.getAmountTransferred().compareTo(BigDecimal.ZERO) == 0) { 201 if(transfer.getForfeitedAmount() != null && transfer.getForfeitedAmount().signum() != 0) 202 allMessages.get("infoMessages").add("A transfer action that forfeited leave occured on this calendar"); //info 203 } 204 else 205 allMessages.get("infoMessages").add("A transfer action occurred on this calendar"); //info 206 } 207 } 208 } 209 210 List<LeavePayout> payouts = TkServiceLocator.getLeavePayoutService().getLeavePayouts(principalId, fromDate, toDate); 211 for(LeavePayout payout : payouts) { 212 if(StringUtils.equals(payout.getStatus(), TkConstants.ROUTE_STATUS.ENROUTE)) { 213 allMessages.get("actionMessages").add("A pending payout exists on this calendar. It must be finalized before this calendar can be approved"); 214 } 215 if(StringUtils.equals(payout.getStatus() ,TkConstants.ROUTE_STATUS.FINAL)) { 216 if(payout.getPayoutAmount().compareTo(BigDecimal.ZERO) == 0) { 217 if(payout.getForfeitedAmount() != null && payout.getForfeitedAmount().signum() != 0) 218 allMessages.get("infoMessages").add("A payout action that forfeited leave occured on this calendar"); 219 } 220 else 221 allMessages.get("infoMessages").add("A payout action occurred on this calendar"); 222 } 223 if(StringUtils.equals(payout.getStatus() ,TkConstants.ROUTE_STATUS.DISAPPROVED)) { 224 if(payout.getPayoutAmount().compareTo(BigDecimal.ZERO) == 0) { 225 if(payout.getForfeitedAmount() != null && payout.getForfeitedAmount().signum() != 0) 226 allMessages.get("infoMessages").add("A disapproved payout that forfeited leave occured on this calendar"); 227 } 228 else 229 allMessages.get("infoMessages").add("A disapproved payout occurred on this calendar"); 230 } 231 }*/ 232 233 return allMessages; 234 } 235 236 // get warning messages associated with earn codes of leave blocks 237 public static Map<String, Set<String>> getWarningMessagesForLeaveBlocks(List<LeaveBlock> leaveBlocks) { 238 // List<String> warningMessages = new ArrayList<String>(); 239 Map<String, Set<String>> allMessages = new HashMap<String, Set<String>>(); 240 241 Set<String> actionMessages = new HashSet<String>(); 242 Set<String> infoMessages = new HashSet<String>(); 243 Set<String> warningMessages = new HashSet<String>(); 244 245 if (CollectionUtils.isNotEmpty(leaveBlocks)) { 246 for(LeaveBlock lb : leaveBlocks) { 247 EarnCode ec = TkServiceLocator.getEarnCodeService().getEarnCode(lb.getEarnCode(), lb.getLeaveDate()); 248 if(ec != null) { 249 EarnCodeGroup eg = TkServiceLocator.getEarnCodeGroupService().getEarnCodeGroupForEarnCode(lb.getEarnCode(), lb.getLeaveDate()); 250 if(eg != null && !StringUtils.isEmpty(eg.getWarningText())) { 251 warningMessages.add(eg.getWarningText()); 252 } 253 } 254 } 255 } 256 allMessages.put("actionMessages", actionMessages); 257 allMessages.put("infoMessages", infoMessages); 258 allMessages.put("warningMessages", warningMessages); 259 260 // warningMessages.addAll(aSet); 261 return allMessages; 262 } 263 264 public static List<String> validateAvailableLeaveBalance(LeaveCalendarWSForm lcf) { 265 LeaveBlock updatedLeaveBlock = null; 266 if(lcf.getLeaveBlockId() != null) { 267 updatedLeaveBlock = TkServiceLocator.getLeaveBlockService().getLeaveBlock(lcf.getLeaveBlockId()); 268 } 269 return validateAvailableLeaveBalanceForUsage(lcf.getSelectedEarnCode(), lcf.getStartDate(), lcf.getEndDate(), lcf.getLeaveAmount(), updatedLeaveBlock); 270 } 271 272 public static List<String> validateAvailableLeaveBalanceForUsage(String earnCode, String leaveStartDateString, String leaveEndDateString, 273 BigDecimal leaveAmount, LeaveBlock updatedLeaveBlock) { 274 List<String> errors = new ArrayList<String>(); 275 boolean earnCodeChanged = false; 276 BigDecimal oldAmount = null; 277 278 if(updatedLeaveBlock != null) { 279 if(!updatedLeaveBlock.getEarnCode().equals(earnCode)) { 280 earnCodeChanged = true; 281 } 282 if(!updatedLeaveBlock.getLeaveAmount().equals(leaveAmount)) { 283 oldAmount = updatedLeaveBlock.getLeaveAmount(); 284 } 285 } 286 Date startDate = TKUtils.formatDateString(leaveStartDateString); 287 Date endDate = TKUtils.formatDateString(leaveEndDateString); 288 long daysSpan = TKUtils.getDaysBetween(startDate,endDate); 289 EarnCode earnCodeObj = TkServiceLocator.getEarnCodeService().getEarnCode(earnCode, endDate); 290 if(earnCodeObj != null && earnCodeObj.getAllowNegativeAccrualBalance().equals("N")) { 291 AccrualCategory accrualCategory = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(earnCodeObj.getAccrualCategory(), endDate); 292 if(accrualCategory != null) { 293 java.util.Date nextIntervalDate = TkServiceLocator.getAccrualService().getNextAccrualIntervalDate(accrualCategory.getAccrualEarnInterval(), endDate); 294 // get the usage checking cut off Date, normally it's the day before the next interval date 295 java.util.Date usageEndDate = nextIntervalDate; 296 if(nextIntervalDate.compareTo(endDate) > 0) { 297 Calendar aCal = Calendar.getInstance(); 298 aCal.setTime(nextIntervalDate); 299 aCal.add(Calendar.DAY_OF_YEAR, -1); 300 usageEndDate = aCal.getTime(); 301 } 302 // use the end of the year as the interval date for usage checking of no-accrual hours, 303 // normally no-accrual hours are from banked/transferred system scheduled time offs 304 if(accrualCategory.getAccrualEarnInterval().equals(LMConstants.ACCRUAL_EARN_INTERVAL_CODE.NO_ACCRUAL)) { 305 Calendar aCal = Calendar.getInstance(); 306 aCal.setTime(endDate); 307 aCal.set(Calendar.MONTH, Calendar.DECEMBER); 308 aCal.set(Calendar.DAY_OF_MONTH, 31); 309 nextIntervalDate = aCal.getTime(); 310 usageEndDate = nextIntervalDate; 311 } 312 BigDecimal availableBalance = TkServiceLocator.getLeaveSummaryService() 313 .getLeaveBalanceForAccrCatUpToDate(TKContext.getTargetPrincipalId(), startDate, endDate, accrualCategory.getAccrualCategory(), usageEndDate); 314 315 if(oldAmount!=null) { 316 if(!earnCodeChanged || 317 updatedLeaveBlock.getAccrualCategory().equals(accrualCategory.getAccrualCategory())) { 318 availableBalance = availableBalance.add(oldAmount.abs()); 319 } 320 } 321 //multiply by days in span in case the user has also edited the start/end dates. 322 BigDecimal desiredUsage = leaveAmount.multiply(new BigDecimal(daysSpan+1)); 323 if(desiredUsage.compareTo(availableBalance) > 0 ) { 324 errors.add("Requested leave amount " + desiredUsage.toString() + " is greater than available leave balance " + availableBalance.toString()); //errorMessages 325 } 326 } 327 } 328 329 return errors; 330 } 331 332 // KPME-2010 333 public static List<String> validateSpanningWeeks(LeaveCalendarWSForm lcf) { 334 boolean spanningWeeks = lcf.getSpanningWeeks().equalsIgnoreCase("y"); 335 DateTime startTemp = new DateTime(TKUtils.convertDateStringToTimestamp(lcf.getStartDate()).getTime()); 336 DateTime endTemp = new DateTime(TKUtils.convertDateStringToTimestamp(lcf.getEndDate()).getTime()); 337 338 List<String> errors = new ArrayList<String>(); 339 boolean valid = true; 340 while ((startTemp.isBefore(endTemp) || startTemp.isEqual(endTemp)) && valid) { 341 if (!spanningWeeks && 342 (startTemp.getDayOfWeek() == DateTimeConstants.SATURDAY || startTemp.getDayOfWeek() == DateTimeConstants.SUNDAY)) { 343 valid = false; 344 } 345 startTemp = startTemp.plusDays(1); 346 } 347 if (!valid) { 348 errors.add("Weekend day is selected, but include weekends checkbox is not checked"); //errorMessages 349 } 350 return errors; 351 } 352 }