View Javadoc
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 }