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 }