001 /** 002 * Copyright 2004-2014 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.kpme.tklm.leave.transfer.validation; 017 018 import java.math.BigDecimal; 019 import java.util.ArrayList; 020 import java.util.Date; 021 import java.util.HashMap; 022 import java.util.HashSet; 023 import java.util.List; 024 import java.util.Map; 025 import java.util.Set; 026 027 import org.apache.commons.lang.StringUtils; 028 import org.apache.commons.lang.time.DateUtils; 029 import org.joda.time.DateTime; 030 import org.joda.time.LocalDate; 031 import org.kuali.kpme.core.KPMENamespace; 032 import org.kuali.kpme.core.accrualcategory.AccrualCategory; 033 import org.kuali.kpme.core.accrualcategory.rule.AccrualCategoryRule; 034 import org.kuali.kpme.core.assignment.Assignment; 035 import org.kuali.kpme.core.department.Department; 036 import org.kuali.kpme.core.job.Job; 037 import org.kuali.kpme.core.permission.KPMEPermissionTemplate; 038 import org.kuali.kpme.core.principal.PrincipalHRAttributes; 039 import org.kuali.kpme.core.role.KPMERole; 040 import org.kuali.kpme.core.role.KPMERoleMemberAttribute; 041 import org.kuali.kpme.core.service.HrServiceLocator; 042 import org.kuali.kpme.core.util.HrConstants; 043 import org.kuali.kpme.core.util.HrContext; 044 import org.kuali.kpme.core.util.ValidationUtils; 045 import org.kuali.kpme.tklm.common.TkConstants; 046 import org.kuali.kpme.tklm.leave.override.EmployeeOverride; 047 import org.kuali.kpme.tklm.leave.service.LmServiceLocator; 048 import org.kuali.kpme.tklm.leave.summary.LeaveSummary; 049 import org.kuali.kpme.tklm.leave.summary.LeaveSummaryRow; 050 import org.kuali.kpme.tklm.leave.transfer.BalanceTransfer; 051 import org.kuali.kpme.tklm.time.service.TkServiceLocator; 052 import org.kuali.kpme.tklm.time.util.TkContext; 053 import org.kuali.rice.kim.api.KimConstants; 054 import org.kuali.rice.kim.api.identity.Person; 055 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 056 import org.kuali.rice.kns.document.MaintenanceDocument; 057 import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; 058 import org.kuali.rice.krad.bo.PersistableBusinessObject; 059 import org.kuali.rice.krad.util.GlobalVariables; 060 import org.kuali.rice.krad.util.ObjectUtils; 061 062 public class BalanceTransferValidation extends MaintenanceDocumentRuleBase { 063 064 /* 065 //the "to" and "from" accrual categories should be in the supplied principal's leave plan as of the effective date. 066 private boolean validateLeavePlan(PrincipalHRAttributes pha, 067 AccrualCategory fromAccrualCategory, AccrualCategory toAccrualCategory, LocalDate effectiveDate) { 068 boolean isValid = true; 069 if(fromAccrualCategory == null || toAccrualCategory == null) { 070 return false; 071 } 072 List<AccrualCategory> accrualCategories = HrServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), effectiveDate); 073 if(accrualCategories.size() > 0) { 074 boolean isFromInLeavePlan = false; 075 boolean isToInLeavePlan = false; 076 for(AccrualCategory activeAccrualCategory : accrualCategories) { 077 if(StringUtils.equals(activeAccrualCategory.getLmAccrualCategoryId(),fromAccrualCategory.getLmAccrualCategoryId())) { 078 isFromInLeavePlan = true; 079 } 080 if(StringUtils.equals(activeAccrualCategory.getLmAccrualCategoryId(), toAccrualCategory.getLmAccrualCategoryId())) { 081 isToInLeavePlan = true; 082 } 083 } 084 if(!isFromInLeavePlan) { 085 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "balanceTransfer.accrualCategory.notInLeavePlan", fromAccrualCategory.getAccrualCategory()); 086 isValid &= false; 087 } 088 if(!isToInLeavePlan) { 089 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.accrualCategory.notInLeavePlan", toAccrualCategory.getAccrualCategory()); 090 isValid &= false; 091 } 092 } 093 else { 094 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.principal.noACinLeavePlan"); 095 isValid &=false; 096 } 097 return isValid; 098 } 099 100 //See isTransferAmountUnderMaxLimit for futher validation 101 private boolean validateTransferAmount(BigDecimal transferAmount, 102 AccrualCategory debitedAccrualCategory, 103 AccrualCategory creditedAccrualCategory, String principalId, LocalDate effectiveDate) { 104 if(transferAmount != null) { 105 if(transferAmount.compareTo(BigDecimal.ZERO) < 0 ) { 106 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "balanceTransfer.amount.negative"); 107 return false; 108 } 109 } 110 111 return true; 112 } 113 114 //Effective date not more than one year in advance 115 private boolean validateEffectiveDate(LocalDate date) { 116 if(date != null) { 117 if(DateUtils.addYears(LocalDate.now().toDate(), 1).compareTo(date.toDate()) > 0) 118 return true; 119 else 120 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.effectiveDate", "balanceTransfer.effectiveDate.error"); 121 } 122 return false; 123 } 124 125 private boolean validateTransferFromAccrualCategory(AccrualCategory accrualCategory, String principalId, 126 LocalDate effectiveDate, AccrualCategoryRule acr, String fromAccrualCategory) { 127 boolean isValid = true; 128 if(accrualCategory == null) { 129 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "balanceTransfer.accrualcategory.exists",fromAccrualCategory); 130 isValid &= false; 131 } 132 return isValid; 133 } 134 135 //Transfer to accrual category should match the value defined in the accrual category rule 136 private boolean validateTransferToAccrualCategory(AccrualCategory accrualCategory, String principalId, LocalDate effectiveDate, AccrualCategoryRule acr, String toAccrualCategory, boolean isSomeAdmin) { 137 boolean isValid = true; 138 if(accrualCategory != null) { 139 if(acr != null) { 140 //processCustomRouteDocumentBusinessRule will provide the invalidation on system triggered transfers 141 //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. 142 AccrualCategory maxBalTranToAccCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(acr.getMaxBalanceTransferToAccrualCategory(),effectiveDate); 143 if(!StringUtils.equals(maxBalTranToAccCat.getLmAccrualCategoryId(),accrualCategory.getLmAccrualCategoryId()) && !isSomeAdmin) { 144 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.toAccrualCategory.noMatch",accrualCategory.getAccrualCategory()); 145 isValid &= false; 146 } 147 } 148 } 149 else { 150 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.accrualcategory.exists",toAccrualCategory); 151 isValid &= false; 152 } 153 return isValid; 154 } 155 156 //transfer amount must be under max limit when submitted via max balance triggered action or by a work area approver. 157 private boolean isTransferAmountUnderMaxLimit(String principalId, LocalDate effectiveDate, String accrualCategory, 158 BigDecimal transferAmount, AccrualCategoryRule accrualRule, String leavePlan) { 159 160 if(ObjectUtils.isNotNull(accrualRule)) { 161 162 BigDecimal maxTransferAmount = null; 163 if(ObjectUtils.isNotNull(accrualRule.getMaxTransferAmount())) { 164 maxTransferAmount = new BigDecimal(accrualRule.getMaxTransferAmount()); 165 } 166 if(ObjectUtils.isNotNull(maxTransferAmount)) { 167 EmployeeOverride eo = LmServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, leavePlan, accrualCategory, TkConstants.EMPLOYEE_OVERRIDE_TYPE.get("MTA"), effectiveDate); 168 if(ObjectUtils.isNotNull(eo)) 169 if(ObjectUtils.isNull(eo.getOverrideValue())) 170 maxTransferAmount = new BigDecimal(Long.MAX_VALUE); 171 else 172 maxTransferAmount = new BigDecimal(eo.getOverrideValue()); 173 else { 174 BigDecimal fteSum = HrServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate); 175 maxTransferAmount = maxTransferAmount.multiply(fteSum); 176 } 177 if(transferAmount.compareTo(maxTransferAmount) > 0) { 178 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount","balanceTransfer.exceeds.transferLimit"); 179 return false; 180 } 181 } 182 } 183 return true; 184 } 185 186 @Override 187 public boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) { 188 boolean isValid = super.processCustomRouteDocumentBusinessRules(document); 189 LOG.debug("entering custom validation for Balance Transfer"); 190 191 PersistableBusinessObject pbo = (PersistableBusinessObject) this.getNewBo(); 192 193 if(pbo instanceof BalanceTransfer) { 194 195 BalanceTransfer balanceTransfer = (BalanceTransfer) pbo; 196 197 // if this balance transfer is on a system scheduled time off, then don't do further validation 198 if(StringUtils.isNotEmpty(balanceTransfer.getSstoId())) { 199 isValid &= BalanceTransferValidationUtils.validateSstoTranser(balanceTransfer); 200 return isValid; 201 } 202 if(isValid) { 203 204 *//** 205 * Validation is basically governed by accrual category rules. Get accrual category 206 * rules for both the "To" and "From" accrual categories, pass to validators along with the 207 * values needing to be validated. 208 * 209 * Balance transfers initiated from the leave calendar display should already have all values 210 * populated, thus validated, including the accrual category rule for the "From" accrual category. 211 * 212 * Balance transfers initiated via the Maintenance tab will have no values populated. 213 *//* 214 String principalId = balanceTransfer.getPrincipalId(); 215 LocalDate effectiveDate = balanceTransfer.getEffectiveLocalDate(); 216 String fromAccrualCategory = balanceTransfer.getFromAccrualCategory(); 217 String toAccrualCategory = balanceTransfer.getToAccrualCategory(); 218 AccrualCategory fromCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(fromAccrualCategory, effectiveDate); 219 AccrualCategory toCat = HrServiceLocator.getAccrualCategoryService().getAccrualCategory(toAccrualCategory, effectiveDate); 220 PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId,effectiveDate); 221 222 // TODO Check for role in specific dept / location 223 boolean isDeptAdmin = TkContext.isDepartmentAdmin(); 224 boolean isSysAdmin = HrContext.isSystemAdmin(); 225 boolean isLocAdmin = TkContext.isLocationAdmin(); 226 227 if(ObjectUtils.isNotNull(pha)) { 228 if(isDeptAdmin || isSysAdmin || isLocAdmin) { 229 isValid &= validateLeavePlan(pha,fromCat,toCat,effectiveDate); 230 isValid &= validatePrincipal(pha,principalId); 231 isValid &= validateTransferFromAccrualCategory(fromCat,principalId,effectiveDate,null,fromAccrualCategory); 232 isValid &= validateTransferToAccrualCategory(toCat,principalId,effectiveDate,null,toAccrualCategory,true); 233 isValid &= validateTransferAmount(balanceTransfer.getTransferAmount(),fromCat,toCat, principalId, effectiveDate); 234 } 235 else { 236 if(ObjectUtils.isNotNull(pha.getLeavePlan())) { 237 AccrualCategoryRule acr = HrServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(fromCat, 238 effectiveDate, pha.getServiceLocalDate()); 239 if(ObjectUtils.isNotNull(acr)) { 240 if(StringUtils.isNotBlank(acr.getMaxBalFlag()) 241 && StringUtils.equals(acr.getMaxBalFlag(), "Y")) { 242 if(ObjectUtils.isNotNull(toCat)) { 243 244 isValid &= validatePrincipal(pha,principalId); 245 isValid &= validateEffectiveDate(effectiveDate); 246 isValid &= validateLeavePlan(pha,fromCat,toCat,effectiveDate); 247 isValid &= validateTransferFromAccrualCategory(fromCat,principalId,effectiveDate,acr,fromAccrualCategory); 248 isValid &= validateTransferToAccrualCategory(toCat,principalId,effectiveDate,acr,toAccrualCategory,false); 249 isValid &= validateTransferAmount(balanceTransfer.getTransferAmount(),fromCat,toCat, null, null); 250 isValid &= isTransferAmountUnderMaxLimit(principalId,effectiveDate,fromAccrualCategory,balanceTransfer.getTransferAmount(),acr,pha.getLeavePlan()); 251 } 252 else { 253 //should never be the case if accrual category rules are validated correctly. 254 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", 255 "balanceTransfer.fromAccrualCategory.rules.transferToAccrualCategory", 256 fromAccrualCategory); 257 isValid &= false; 258 } 259 } 260 else { 261 //max bal flag null, blank, empty, or "N" 262 GlobalVariables.getMessageMap().putError("document.newMaintinableObject.fromAccrualCategory", 263 "balanceTransfer.fromAccrualCategory.rules.maxBalFlag", fromAccrualCategory); 264 isValid &= false; 265 } 266 } 267 else { 268 //department admins must validate amount to transfer does not exceed current balance. 269 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", 270 "balanceTransfer.fromAccrualCategory.rules.exist",fromCat.getAccrualCategory()); 271 isValid &= false; 272 } 273 } 274 else { 275 //if the principal doesn't have a leave plan, there aren't any accrual categories that can be debited/credited. 276 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId","balanceTransfer.principal.noLeavePlan"); 277 isValid &=false; 278 } 279 } 280 } 281 else { 282 //if the principal has no principal hr attributes, they're not a principal. 283 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId","balanceTransfer.principal.noAttributes"); 284 isValid &= false; 285 } 286 287 } 288 } 289 return isValid; 290 }*/ 291 292 @Override 293 protected boolean processCustomRouteDocumentBusinessRules( 294 MaintenanceDocument document) { 295 boolean isValid = super.processCustomRouteDocumentBusinessRules(document); 296 297 LOG.debug("entering custom validation for Balance Transfer"); 298 299 PersistableBusinessObject pbo = (PersistableBusinessObject) this.getNewBo(); 300 301 if(pbo instanceof BalanceTransfer) { 302 303 BalanceTransfer balanceTransfer = (BalanceTransfer) pbo; 304 String fromAccrualCat = balanceTransfer.getFromAccrualCategory(); 305 String toAccrualCat = balanceTransfer.getToAccrualCategory(); 306 String principalId = balanceTransfer.getPrincipalId(); 307 BigDecimal transferAmount = balanceTransfer.getTransferAmount(); 308 309 isValid &= validateEffectiveDate(balanceTransfer.getEffectiveLocalDate()); 310 isValid &= validateFromAccrualCateogry(fromAccrualCat,balanceTransfer.getEffectiveLocalDate()); 311 isValid &= validateToAccrualCateogry(toAccrualCat,balanceTransfer.getEffectiveLocalDate()); 312 isValid &= validateTransferAmount(principalId,transferAmount,fromAccrualCat,balanceTransfer.getEffectiveLocalDate()); 313 if(validatePrincipalId(principalId,balanceTransfer.getEffectiveLocalDate())) { 314 isValid &= validatePrincipal(principalId,balanceTransfer.getEffectiveDate(),GlobalVariables.getUserSession().getPrincipalId()); 315 } 316 else { 317 isValid &= false; 318 } 319 } 320 return isValid; 321 } 322 323 //Effective date not more than one year in advance 324 private boolean validateEffectiveDate(LocalDate date) { 325 if(date != null) { 326 if(date.isAfter(LocalDate.now().plusYears(1))) { 327 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.effectiveDate", "balanceTransfer.effectiveDate.error"); 328 return false; 329 } 330 } 331 return true; 332 } 333 334 private boolean validateFromAccrualCateogry(String fromAccrualCat, 335 LocalDate effectiveLocalDate) { 336 boolean isValid = true; 337 if(StringUtils.isNotEmpty(fromAccrualCat)) { 338 isValid &= ValidationUtils.validateAccCategory(fromAccrualCat, effectiveLocalDate); 339 if(!isValid) { 340 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "balanceTransfer.accrualcategory.exists", fromAccrualCat); 341 } 342 } 343 return isValid; 344 } 345 346 private boolean validateToAccrualCateogry(String toAccrualCat, 347 LocalDate effectiveLocalDate) { 348 boolean isValid = true; 349 if(StringUtils.isNotEmpty(toAccrualCat)) { 350 isValid &= ValidationUtils.validateAccCategory(toAccrualCat, effectiveLocalDate); 351 if(!isValid) { 352 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.toAccrualCategory", "balanceTransfer.accrualcategory.exists", toAccrualCat); 353 } 354 } 355 return isValid; 356 } 357 358 private boolean validateTransferAmount(String principalId, BigDecimal transferAmount, 359 String fromAccrualCat, LocalDate effectiveLocalDate) { 360 boolean isValid = true; 361 if(transferAmount != null) { 362 LeaveSummary leaveSummary = LmServiceLocator.getLeaveSummaryService().getLeaveSummaryAsOfDateForAccrualCategory(principalId, effectiveLocalDate, fromAccrualCat); 363 if(leaveSummary != null) { 364 LeaveSummaryRow leaveSummaryRow = leaveSummary.getLeaveSummaryRowForAccrualCtgy(fromAccrualCat); 365 if(leaveSummaryRow != null) { 366 BigDecimal accruedBalance = leaveSummaryRow.getAccruedBalance(); 367 if(transferAmount.compareTo(accruedBalance) > 0) { 368 isValid &= false; 369 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "balanceTransfer.transferAmount.exceedsBalance"); 370 } 371 } 372 } 373 if(transferAmount.compareTo(BigDecimal.ZERO) < 0 ) { 374 isValid &= false; 375 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "balanceTransfer.transferAmount.negative"); 376 } 377 } 378 return isValid; 379 } 380 381 private boolean validatePrincipalId(String principalId, 382 LocalDate effectiveLocalDate) { 383 boolean isValid = true; 384 if(StringUtils.isNotEmpty(principalId)) { 385 isValid &= ValidationUtils.validatePrincipalId(principalId); 386 if(!isValid) { 387 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.principal.exists"); 388 } 389 } 390 return isValid; 391 } 392 393 private boolean validatePrincipal(String principalId, Date effectiveDate, String userPrincipalId) { 394 boolean isValid = true; 395 PrincipalHRAttributes pha = HrServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, LocalDate.fromDateFields(effectiveDate)); 396 397 if(pha == null) { 398 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.principal.noAttributes"); 399 isValid &= false; 400 } 401 else { 402 boolean canCreate = false; 403 if(!StringUtils.equals(principalId,userPrincipalId)) { 404 List<Job> principalsJobs = HrServiceLocator.getJobService().getActiveLeaveJobs(principalId, LocalDate.fromDateFields(effectiveDate)); 405 406 for(Job job : principalsJobs) { 407 408 if(job.isEligibleForLeave()) { 409 410 String department = job != null ? job.getDept() : null; 411 Department departmentObj = job != null ? HrServiceLocator.getDepartmentService().getDepartmentWithoutRoles(department, LocalDate.fromDateFields(effectiveDate)) : null; 412 String location = departmentObj != null ? departmentObj.getLocation() : null; 413 //logged in user may only submit documents for principals in authorized departments / location. 414 if (LmServiceLocator.getLMPermissionService().isAuthorizedInDepartment(userPrincipalId, "Create Balance Transfer", department, new DateTime(effectiveDate.getTime())) 415 || LmServiceLocator.getLMPermissionService().isAuthorizedInLocation(userPrincipalId, "Create Balance Transfer", location, new DateTime(effectiveDate.getTime()))) { 416 canCreate = true; 417 break; 418 } 419 else { 420 //do NOT block approvers, processors, delegates from approving the document. 421 List<Assignment> assignments = HrServiceLocator.getAssignmentService().getActiveAssignmentsForJob(principalId, job.getJobNumber(), LocalDate.fromDateFields(effectiveDate)); 422 for(Assignment assignment : assignments) { 423 if(HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.APPROVER.getRoleName(), assignment.getWorkArea(), new DateTime(effectiveDate)) 424 || HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.APPROVER_DELEGATE.getRoleName(), assignment.getWorkArea(), new DateTime(effectiveDate)) 425 || HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR.getRoleName(), assignment.getWorkArea(), new DateTime(effectiveDate)) 426 || HrServiceLocator.getKPMERoleService().principalHasRoleInWorkArea(userPrincipalId, KPMENamespace.KPME_HR.getNamespaceCode(), KPMERole.PAYROLL_PROCESSOR_DELEGATE.getRoleName(), assignment.getWorkArea(), new DateTime(effectiveDate))) { 427 canCreate = true; 428 break; 429 } 430 } 431 } 432 } 433 } 434 } 435 else { 436 //should be able to submit their own transaction documents... 437 //max balance triggered transactions go through this validation. Set a userPrincipal to system and deny LEAVE DEPT/LOC Admins ability to submit their own 438 //transactions these simplified rules?? 439 canCreate = false; 440 } 441 442 if(!canCreate) { 443 GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "balanceTransfer.userNotAuthorized"); 444 isValid &= false; 445 } 446 } 447 return isValid; 448 } 449 450 }