1 /** 2 * Copyright 2004-2014 The Kuali Foundation 3 * 4 * Licensed under the Educational Community License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.opensource.org/licenses/ecl2.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package org.kuali.kpme.tklm.leave.transfer.validation; 17 18 import org.apache.commons.lang.StringUtils; 19 import org.joda.time.DateTime; 20 import org.joda.time.LocalDate; 21 import org.kuali.kpme.core.api.assignment.Assignment; 22 import org.kuali.kpme.core.api.department.Department; 23 import org.kuali.kpme.core.api.job.Job; 24 import org.kuali.kpme.core.api.namespace.KPMENamespace; 25 import org.kuali.kpme.core.api.principal.PrincipalHRAttributes; 26 import org.kuali.kpme.core.role.KPMERole; 27 import org.kuali.kpme.core.service.HrServiceLocator; 28 import org.kuali.kpme.core.util.ValidationUtils; 29 import org.kuali.kpme.tklm.api.leave.summary.LeaveSummaryContract; 30 import org.kuali.kpme.tklm.api.leave.summary.LeaveSummaryRowContract; 31 import org.kuali.kpme.tklm.leave.service.LmServiceLocator; 32 import org.kuali.kpme.tklm.leave.transfer.BalanceTransfer; 33 import org.kuali.rice.kns.document.MaintenanceDocument; 34 import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; 35 import org.kuali.rice.krad.bo.PersistableBusinessObject; 36 import org.kuali.rice.krad.util.GlobalVariables; 37 38 import java.math.BigDecimal; 39 import java.util.Date; 40 import java.util.List; 41 42 public class BalanceTransferValidation extends MaintenanceDocumentRuleBase { 43 44 /* 45 //the "to" and "from" accrual categories should be in the supplied principal's leave plan as of the effective date. 46 private boolean validateLeavePlan(PrincipalHRAttributes pha, 47 AccrualCategory fromAccrualCategory, AccrualCategory toAccrualCategory, LocalDate effectiveDate) { 48 boolean isValid = true; 49 if(fromAccrualCategory == null || toAccrualCategory == null) { 50 return false; 51 } 52 List<AccrualCategory> accrualCategories = HrServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), effectiveDate); 53 if(accrualCategories.size() > 0) { 54 boolean isFromInLeavePlan = false; 55 boolean isToInLeavePlan = false; 56 for(AccrualCategory activeAccrualCategory : accrualCategories) { 57 if(StringUtils.equals(activeAccrualCategory.getLmAccrualCategoryId(),fromAccrualCategory.getLmAccrualCategoryId())) { 58 isFromInLeavePlan = true; 59 } 60 if(StringUtils.equals(activeAccrualCategory.getLmAccrualCategoryId(), toAccrualCategory.getLmAccrualCategoryId())) { 61 isToInLeavePlan = true; 62 } 63 } 64 if(!isFromInLeavePlan) { 65 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "balanceTransfer.accrualCategory.notInLeavePlan", fromAccrualCategory.getAccrualCategory()); 66 isValid &= false; 67 } 68 if(!isToInLeavePlan) { 69 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.accrualCategory.notInLeavePlan", toAccrualCategory.getAccrualCategory()); 70 isValid &= false; 71 } 72 } 73 else { 74 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.principal.noACinLeavePlan"); 75 isValid &=false; 76 } 77 return isValid; 78 } 79 80 //See isTransferAmountUnderMaxLimit for futher validation 81 private boolean validateTransferAmount(BigDecimal transferAmount, 82 AccrualCategory debitedAccrualCategory, 83 AccrualCategory creditedAccrualCategory, String principalId, LocalDate effectiveDate) { 84 if(transferAmount != null) { 85 if(transferAmount.compareTo(BigDecimal.ZERO) < 0 ) { 86 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "balanceTransfer.amount.negative"); 87 return false; 88 } 89 } 90 91 return true; 92 } 93 94 //Effective date not more than one year in advance 95 private boolean validateEffectiveDate(LocalDate date) { 96 if(date != null) { 97 if(DateUtils.addYears(LocalDate.now().toDate(), 1).compareTo(date.toDate()) > 0) 98 return true; 99 else 100 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.effectiveDate", "balanceTransfer.effectiveDate.error"); 101 } 102 return false; 103 } 104 105 private boolean validateTransferFromAccrualCategory(AccrualCategory accrualCategory, String principalId, 106 LocalDate effectiveDate, AccrualCategoryRule acr, String fromAccrualCategory) { 107 boolean isValid = true; 108 if(accrualCategory == null) { 109 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "balanceTransfer.accrualcategory.exists",fromAccrualCategory); 110 isValid &= false; 111 } 112 return isValid; 113 } 114 115 //Transfer to accrual category should match the value defined in the accrual category rule 116 private boolean validateTransferToAccrualCategory(AccrualCategory accrualCategory, String principalId, LocalDate effectiveDate, AccrualCategoryRule acr, String toAccrualCategory, boolean isSomeAdmin) { 117 boolean isValid = true; 118 if(accrualCategory != null) { 119 if(acr != null) { 120 //processCustomRouteDocumentBusinessRule will provide the invalidation on system triggered transfers 121 //if the accrual category rule is null, i.o.w. this code block should never be reached when acr is null on sys triggered transfers. 122 AccrualCategory maxBalTranToAccCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(acr.getMaxBalanceTransferToAccrualCategory(),effectiveDate); 123 if(!StringUtils.equals(maxBalTranToAccCat.getLmAccrualCategoryId(),accrualCategory.getLmAccrualCategoryId()) && !isSomeAdmin) { 124 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.toAccrualCategory.noMatch",accrualCategory.getAccrualCategory()); 125 isValid &= false; 126 } 127 } 128 } 129 else { 130 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.accrualcategory.exists",toAccrualCategory); 131 isValid &= false; 132 } 133 return isValid; 134 } 135 136 //transfer amount must be under max limit when submitted via max balance triggered action or by a work area approver. 137 private boolean isTransferAmountUnderMaxLimit(String principalId, LocalDate effectiveDate, String accrualCategory, 138 BigDecimal transferAmount, AccrualCategoryRule accrualRule, String leavePlan) { 139 140 if(ObjectUtils.isNotNull(accrualRule)) { 141 142 BigDecimal maxTransferAmount = null; 143 if(ObjectUtils.isNotNull(accrualRule.getMaxTransferAmount())) { 144 maxTransferAmount = new BigDecimal(accrualRule.getMaxTransferAmount()); 145 } 146 if(ObjectUtils.isNotNull(maxTransferAmount)) { 147 EmployeeOverride eo = LmServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, leavePlan, accrualCategory, TkConstants.EMPLOYEE_OVERRIDE_TYPE.get("MTA"), effectiveDate); 148 if(ObjectUtils.isNotNull(eo)) 149 if(ObjectUtils.isNull(eo.getOverrideValue())) 150 maxTransferAmount = new BigDecimal(Long.MAX_VALUE); 151 else 152 maxTransferAmount = new BigDecimal(eo.getOverrideValue()); 153 else { 154 BigDecimal fteSum = HrServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate); 155 maxTransferAmount = maxTransferAmount.multiply(fteSum); 156 } 157 if(transferAmount.compareTo(maxTransferAmount) > 0) { 158 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount","balanceTransfer.exceeds.transferLimit"); 159 return false; 160 } 161 } 162 } 163 return true; 164 } 165 166 @Override 167 public boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 168 boolean isValid = super.processCustomRouteDocumentBusinessRules(document); 169 LOG.debug("entering custom validation for Balance Transfer"); 170 171 PersistableBusinessObject pbo = (PersistableBusinessObject) this.getNewBo(); 172 173 if(pbo instanceof BalanceTransfer) { 174 175 BalanceTransfer balanceTransfer = (BalanceTransfer) pbo; 176 177 // if this balance transfer is on a system scheduled time off, then don't do further validation 178 if(StringUtils.isNotEmpty(balanceTransfer.getSstoId())) { 179 isValid &= BalanceTransferValidationUtils.validateSstoTranser(balanceTransfer); 180 return isValid; 181 } 182 if(isValid) { 183 184 *//** 185 * Validation is basically governed by accrual category rules. Get accrual category 186 * rules for both the "To" and "From" accrual categories, pass to validators along with the 187 * values needing to be validated. 188 * 189 * Balance transfers initiated from the leave calendar display should already have all values 190 * populated, thus validated, including the accrual category rule for the "From" accrual category. 191 * 192 * Balance transfers initiated via the Maintenance tab will have no values populated. 193 *//* 194 String principalId = balanceTransfer.getPrincipalId(); 195 LocalDate effectiveDate = balanceTransfer.getEffectiveLocalDate(); 196 String fromAccrualCategory = balanceTransfer.getFromAccrualCategory(); 197 String toAccrualCategory = balanceTransfer.getToAccrualCategory(); 198 AccrualCategory fromCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(fromAccrualCategory, effectiveDate); 199 AccrualCategory toCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(toAccrualCategory, effectiveDate); 200 PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId,effectiveDate); 201 202 // TODO Check for role in specific dept / location 203 boolean isDeptAdmin = TkContext.isDepartmentAdmin(); 204 boolean isSysAdmin = HrContext.isSystemAdmin(); 205 boolean isLocAdmin = TkContext.isLocationAdmin(); 206 207 if(ObjectUtils.isNotNull(pha)) { 208 if(isDeptAdmin || isSysAdmin || isLocAdmin) { 209 isValid &= validateLeavePlan(pha,fromCat,toCat,effectiveDate); 210 isValid &= validatePrincipal(pha,principalId); 211 isValid &= validateTransferFromAccrualCategory(fromCat,principalId,effectiveDate,null,fromAccrualCategory); 212 isValid &= validateTransferToAccrualCategory(toCat,principalId,effectiveDate,null,toAccrualCategory,true); 213 isValid &= validateTransferAmount(balanceTransfer.getTransferAmount(),fromCat,toCat, principalId, effectiveDate); 214 } 215 else { 216 if(ObjectUtils.isNotNull(pha.getLeavePlan())) { 217 AccrualCategoryRule acr = HrServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(fromCat, 218 effectiveDate, pha.getServiceLocalDate()); 219 if(ObjectUtils.isNotNull(acr)) { 220 if(StringUtils.isNotBlank(acr.getMaxBalFlag()) 221 && StringUtils.equals(acr.getMaxBalFlag(), "Y")) { 222 if(ObjectUtils.isNotNull(toCat)) { 223 224 isValid &= validatePrincipal(pha,principalId); 225 isValid &= validateEffectiveDate(effectiveDate); 226 isValid &= validateLeavePlan(pha,fromCat,toCat,effectiveDate); 227 isValid &= validateTransferFromAccrualCategory(fromCat,principalId,effectiveDate,acr,fromAccrualCategory); 228 isValid &= validateTransferToAccrualCategory(toCat,principalId,effectiveDate,acr,toAccrualCategory,false); 229 isValid &= validateTransferAmount(balanceTransfer.getTransferAmount(),fromCat,toCat, null, null); 230 isValid &= isTransferAmountUnderMaxLimit(principalId,effectiveDate,fromAccrualCategory,balanceTransfer.getTransferAmount(),acr,pha.getLeavePlan()); 231 } 232 else { 233 //should never be the case if accrual category rules are validated correctly. 234 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", 235 "balanceTransfer.fromAccrualCategory.rules.transferToAccrualCategory", 236 fromAccrualCategory); 237 isValid &= false; 238 } 239 } 240 else { 241 //max bal flag null, blank, empty, or "N" 242 GlobalVariables.getMessageMap().putError("document.newMaintinableObject.fromAccrualCategory", 243 "balanceTransfer.fromAccrualCategory.rules.maxBalFlag", fromAccrualCategory); 244 isValid &= false; 245 } 246 } 247 else { 248 //department admins must validate amount to transfer does not exceed current balance. 249 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", 250 "balanceTransfer.fromAccrualCategory.rules.exist",fromCat.getAccrualCategory()); 251 isValid &= false; 252 } 253 } 254 else { 255 //if the principal doesn't have a leave plan, there aren't any accrual categories that can be debited/credited. 256 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId","balanceTransfer.principal.noLeavePlan"); 257 isValid &=false; 258 } 259 } 260 } 261 else { 262 //if the principal has no principal hr attributes, they're not a principal. 263 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId","balanceTransfer.principal.noAttributes"); 264 isValid &= false; 265 } 266 267 } 268 } 269 return isValid; 270 }*/ 271 272 @Override 273 protected boolean processCustomRouteDocumentBusinessRules( 274 MaintenanceDocument document) { 275 boolean isValid = super.processCustomRouteDocumentBusinessRules(document); 276 277 LOG.debug("entering custom validation for Balance Transfer"); 278 279 PersistableBusinessObject pbo = (PersistableBusinessObject) this.getNewBo(); 280 281 if(pbo instanceof BalanceTransfer) { 282 283 BalanceTransfer balanceTransfer = (BalanceTransfer) pbo; 284 String fromAccrualCat = balanceTransfer.getFromAccrualCategory(); 285 String toAccrualCat = balanceTransfer.getToAccrualCategory(); 286 String principalId = balanceTransfer.getPrincipalId(); 287 BigDecimal transferAmount = balanceTransfer.getTransferAmount(); 288 289 isValid &= validateEffectiveDate(balanceTransfer.getEffectiveLocalDate()); 290 isValid &= validateFromAccrualCateogry(fromAccrualCat,balanceTransfer.getEffectiveLocalDate()); 291 isValid &= validateToAccrualCateogry(toAccrualCat,balanceTransfer.getEffectiveLocalDate()); 292 isValid &= validateTransferAmount(principalId,transferAmount,fromAccrualCat,balanceTransfer.getEffectiveLocalDate()); 293 if(validatePrincipalId(principalId,balanceTransfer.getEffectiveLocalDate())) { 294 isValid &= validatePrincipal(principalId,balanceTransfer.getEffectiveDate(),GlobalVariables.getUserSession().getPrincipalId()); 295 } 296 else { 297 isValid &= false; 298 } 299 } 300 return isValid; 301 } 302 303 //Effective date not more than one year in advance 304 private boolean validateEffectiveDate(LocalDate date) { 305 if(date != null) { 306 if(date.isAfter(LocalDate.now().plusYears(1))) { 307 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.effectiveDate", "balanceTransfer.effectiveDate.error"); 308 return false; 309 } 310 } 311 return true; 312 } 313 314 private boolean validateFromAccrualCateogry(String fromAccrualCat, 315 LocalDate effectiveLocalDate) { 316 boolean isValid = true; 317 if(StringUtils.isNotEmpty(fromAccrualCat)) { 318 isValid &= ValidationUtils.validateAccCategory(fromAccrualCat, effectiveLocalDate); 319 if(!isValid) { 320 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "balanceTransfer.accrualcategory.exists", fromAccrualCat); 321 } 322 } 323 return isValid; 324 } 325 326 private boolean validateToAccrualCateogry(String toAccrualCat, 327 LocalDate effectiveLocalDate) { 328 boolean isValid = true; 329 if(StringUtils.isNotEmpty(toAccrualCat)) { 330 isValid &= ValidationUtils.validateAccCategory(toAccrualCat, effectiveLocalDate); 331 if(!isValid) { 332 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.accrualcategory.exists", toAccrualCat); 333 } 334 } 335 return isValid; 336 } 337 338 private boolean validateTransferAmount(String principalId, BigDecimal transferAmount, 339 String fromAccrualCat, LocalDate effectiveLocalDate) { 340 boolean isValid = true; 341 if(transferAmount != null) { 342 LeaveSummaryContract leaveSummary = LmServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDateForAccrualCategory(principalId, effectiveLocalDate, fromAccrualCat); 343 if(leaveSummary != null) { 344 LeaveSummaryRowContract leaveSummaryRow = leaveSummary.getLeaveSummaryRowForAccrualCtgy(fromAccrualCat); 345 if(leaveSummaryRow != null) { 346 BigDecimal accruedBalance = leaveSummaryRow.getAccruedBalance(); 347 if(transferAmount.compareTo(accruedBalance) > 0) { 348 isValid &= false; 349 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "balanceTransfer.transferAmount.exceedsBalance"); 350 } 351 } 352 } 353 if(transferAmount.compareTo(BigDecimal.ZERO) < 0 ) { 354 isValid &= false; 355 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "balanceTransfer.transferAmount.negative"); 356 } 357 } 358 return isValid; 359 } 360 361 private boolean validatePrincipalId(String principalId, 362 LocalDate effectiveLocalDate) { 363 boolean isValid = true; 364 if(StringUtils.isNotEmpty(principalId)) { 365 isValid &= ValidationUtils.validatePrincipalId(principalId); 366 if(!isValid) { 367 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.principal.exists"); 368 } 369 } 370 return isValid; 371 } 372 373 private boolean validatePrincipal(String principalId, Date effectiveDate, String userPrincipalId) { 374 boolean isValid = true; 375 PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, LocalDate.fromDateFields(effectiveDate)); 376 377 if(pha == null) { 378 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.principal.noAttributes"); 379 isValid = false; 380 } 381 else { 382 boolean canCreate = false; 383 if(!StringUtils.equals(principalId,userPrincipalId)) { 384 List<Job> principalsJobs = HrServiceLocator.getJobService().getActiveLeaveJobs(principalId, LocalDate.fromDateFields(effectiveDate)); 385 386 for(Job job : principalsJobs) { 387 388 if(job.isEligibleForLeave()) { 389 390 String department = job.getDept(); 391 String groupKeyCode = job.getGroupKeyCode(); 392 Department departmentObj = HrServiceLocator.getDepartmentService().getDepartment(department, groupKeyCode, LocalDate.fromDateFields(effectiveDate)); 393 String location = departmentObj != null ? departmentObj.getGroupKey().getLocationId() : null; 394 //logged in user may only submit documents for principals in authorized departments / location. 395 if (LmServiceLocator.getLMPermissionService().isAuthorizedInDepartment(userPrincipalId, "Create Balance Transfer", department, groupKeyCode, new DateTime(effectiveDate.getTime())) 396 || LmServiceLocator.getLMPermissionService().isAuthorizedInLocation(userPrincipalId, "Create Balance Transfer", location, new DateTime(effectiveDate.getTime()))) { 397 canCreate = true; 398 break; 399 } 400 else { 401 //do NOT block approvers, processors, delegates from approving the document. 402 List<Assignment> assignments = HrServiceLocator.getAssignmentService().getActiveAssignmentsForJob(principalId, job.getJobNumber(), LocalDate.fromDateFields(effectiveDate)); 403 for(Assignment assignment : assignments) { 404 if(HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.APPROVER.getRoleName(), assignment.getWorkArea(), new DateTime(effectiveDate)) 405 || HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.APPROVER_DELEGATE.getRoleName(), assignment.getWorkArea(), new DateTime(effectiveDate)) 406 || HrServiceLocator.getKPMERoleService().principalHasRoleInDepartment(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR.getRoleName(), assignment.getDept(), assignment.getGroupKeyCode(), new DateTime(effectiveDate)) 407 || HrServiceLocator.getKPMERoleService().principalHasRoleInDepartment(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR_DELEGATE.getRoleName(), assignment.getDept(), assignment.getGroupKeyCode(), new DateTime(effectiveDate))) { 408 canCreate = true; 409 break; 410 } 411 } 412 } 413 } 414 } 415 } 416 else { 417 //should be able to submit their own transaction documents... 418 //max balance triggered transactions go through this validation. Set a userPrincipal to system and deny LEAVE DEPT/LOC Admins ability to submit their own 419 //transactions these simplified rules?? 420 canCreate = false; 421 } 422 423 if(!canCreate) { 424 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.userNotAuthorized"); 425 isValid &= false; 426 } 427 } 428 return isValid; 429 } 430 431 }