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.leavepayout.validation;
017    
018    import java.math.BigDecimal;
019    import java.sql.Date;
020    import java.util.List;
021    
022    import org.apache.commons.lang.StringUtils;
023    import org.apache.commons.lang.time.DateUtils;
024    import org.kuali.hr.lm.accrual.AccrualCategory;
025    import org.kuali.hr.lm.accrual.AccrualCategoryRule;
026    import org.kuali.hr.lm.employeeoverride.EmployeeOverride;
027    import org.kuali.hr.lm.leavepayout.LeavePayout;
028    import org.kuali.hr.time.earncode.EarnCode;
029    import org.kuali.hr.time.principal.PrincipalHRAttributes;
030    import org.kuali.hr.time.service.base.TkServiceLocator;
031    import org.kuali.hr.time.util.TKContext;
032    import org.kuali.hr.time.util.TKUser;
033    import org.kuali.hr.time.util.TKUtils;
034    import org.kuali.hr.time.util.TkConstants;
035    import org.kuali.rice.kns.document.MaintenanceDocument;
036    import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase;
037    import org.kuali.rice.krad.bo.PersistableBusinessObject;
038    import org.kuali.rice.krad.util.GlobalVariables;
039    import org.kuali.rice.krad.util.ObjectUtils;
040    
041    public class LeavePayoutValidation extends MaintenanceDocumentRuleBase {
042    
043            private boolean validateAgainstLeavePlan(PrincipalHRAttributes pha, AccrualCategory fromAccrualCategory, Date effectiveDate) {
044                    boolean isValid = true;
045    
046                    List<AccrualCategory> accrualCategories = TkServiceLocator.getAccrualCategoryService().getActiveAccrualCategoriesForLeavePlan(pha.getLeavePlan(), effectiveDate);
047                    if(accrualCategories.size() > 0) {
048                            boolean isFromInLeavePlan = false;
049                            for(AccrualCategory activeAccrualCategory : accrualCategories) {
050                                    if(StringUtils.equals(activeAccrualCategory.getLmAccrualCategoryId(),fromAccrualCategory.getLmAccrualCategoryId())) {
051                                            isFromInLeavePlan = true;
052                                    }
053                            }
054                            if(!isFromInLeavePlan) {
055                                    GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory", "leavePayout.accrualCategory.notInLeavePlan", fromAccrualCategory.getAccrualCategory());
056                                    isValid &= false;
057                            }
058                    }
059                    else {
060                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId", "leavePayout.principal.noACinLeavePlan");
061                            isValid &=false;
062                    }
063                    
064                    return isValid;
065            }
066            
067            //Employee Overrides???
068            /**
069             * Transfer amount could be validated against several variables, including max transfer amount,
070             * max carry over ( for the converted amount deposited into the "to" accrual category, max usage
071             * ( if transfers count as usage ).
072             * @param transferAmount
073             * @param debitedAccrualCategory
074             * @param creditedAccrualCategory
075             * @param principalId TODO
076             * @param effectiveDate TODO
077             * @return true if transfer amount is valid
078             */
079            private boolean validatePayoutAmount(BigDecimal transferAmount,
080                            AccrualCategory debitedAccrualCategory,
081                            EarnCode payoutEarnCode, String principalId, Date effectiveDate) {
082                    
083                    if(transferAmount.compareTo(BigDecimal.ZERO) < 0 ) {
084                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.transferAmount", "leavePayout.amount.negative");
085                            return false;
086                    }
087                    //TkServiceLocator.getAccrualCategoryService().getCurrentBalanceForPrincipal(principalId, debitedAccrualCategory, effectiveDate);
088    
089                    return true;
090            }
091    
092            /**
093             * Are there any rules in place for effective date? i.e. not more than one year in advance...
094             * @param date
095             * @return
096             */
097            private boolean validateEffectiveDate(Date date) {
098                    //Limit on future dates?
099                    if(date.getTime() > DateUtils.addYears(TKUtils.getCurrentDate(), 1).getTime()) {
100                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.effectiveDate", "leavePayout.effectiveDate.overOneYear");
101                            return false;
102                    }
103                    return true;
104            }
105            
106            /**
107             * Is the "From" accrual category required to be over its maximum balance before a transfer can take place?
108             * The "From" accrual category must be defined in an accrual category rule as having a max bal rule.
109             * @param accrualCategory
110             * @param effectiveDate 
111             * @param principalId 
112             * @param acr 
113             * @return
114             */
115            private boolean validateTransferFromAccrualCategory(AccrualCategory accrualCategory, String principalId,
116                            Date effectiveDate, AccrualCategoryRule acr) {
117                    //accrualCategory has rules
118                    //PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId, effectiveDate);
119                    
120                    return true;
121            }
122            
123            private boolean validatePrincipal(PrincipalHRAttributes pha, String principalId) {
124                    // TODO Auto-generated method stub
125                    // Principal has to have an associated leave plan.
126                    // leave plan should contain the transfer "from" accrual category.
127                    
128                    return true;
129            }
130            
131            //transfer amount must be under max limit when submitted via max balance triggered action or by a work area approver.
132            private boolean isPayoutAmountUnderMaxLimit(String principalId, Date effectiveDate, String accrualCategory,
133                                    BigDecimal payoutAmount, AccrualCategoryRule accrualRule, String leavePlan) {
134            
135                    if(ObjectUtils.isNotNull(accrualRule)) {
136    
137                            BigDecimal maxPayoutAmount = null;
138                            if(ObjectUtils.isNotNull(accrualRule.getMaxTransferAmount())) {
139                                    maxPayoutAmount = new BigDecimal(accrualRule.getMaxTransferAmount());
140                            }
141                            if(ObjectUtils.isNotNull(maxPayoutAmount)) {
142                                    EmployeeOverride eo = TkServiceLocator.getEmployeeOverrideService().getEmployeeOverride(principalId, leavePlan, accrualCategory, TkConstants.EMPLOYEE_OVERRIDE_TYPE.get("MPA"), effectiveDate);
143                                    if(ObjectUtils.isNotNull(eo))
144                                            if(ObjectUtils.isNull(eo.getOverrideValue()))
145                                                    maxPayoutAmount = new BigDecimal(Long.MAX_VALUE);
146                                            else
147                                                    maxPayoutAmount = new BigDecimal(eo.getOverrideValue());
148                                    else {
149                                            BigDecimal fteSum = TkServiceLocator.getJobService().getFteSumForAllActiveLeaveEligibleJobs(principalId, effectiveDate);
150                                            maxPayoutAmount = maxPayoutAmount.multiply(fteSum);
151                                    }
152                                    if(payoutAmount.compareTo(maxPayoutAmount) > 0) {
153                                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.payoutAmount","leavePayout.exceeds.payoutLimit");
154                                            return false;
155                                    }
156                            }
157                    }
158                    return true;
159            }
160            
161            @Override
162            public boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
163                    boolean isValid = true;
164                    LOG.debug("entering custom validation for Balance Transfer");
165    
166                    PersistableBusinessObject pbo = (PersistableBusinessObject) this.getNewBo();
167    
168                    if(pbo instanceof LeavePayout) {
169    
170                            LeavePayout leavePayout = (LeavePayout) pbo;
171    
172                            /**
173                             * Validation is basically governed by accrual category rules. Get accrual category
174                             * rules for both the "To" and "From" accrual categories, pass to validators along with the
175                             * values needing to be validated.
176                             * 
177                             * Balance transfers initiated from the leave calendar display should already have all values
178                             * populated, thus validated, including the accrual category rule for the "From" accrual category.
179                             * 
180                             * Balance transfers initiated via the Maintenance tab will have no values populated.
181                             */
182                            String principalId = leavePayout.getPrincipalId();
183                            Date effectiveDate = leavePayout.getEffectiveDate();
184                            String fromAccrualCategory = leavePayout.getFromAccrualCategory();
185                            EarnCode payoutEarnCode = leavePayout.getEarnCodeObj();
186                            AccrualCategory fromCat = TkServiceLocator.getAccrualCategoryService().getAccrualCategory(fromAccrualCategory, effectiveDate);
187                            PrincipalHRAttributes pha = TkServiceLocator.getPrincipalHRAttributeService().getPrincipalCalendar(principalId,effectiveDate);
188                            
189                            boolean isDeptAdmin = TKUser.isDepartmentAdmin();
190                            boolean isSysAdmin = TKUser.isSystemAdmin();
191                            if(isDeptAdmin || isSysAdmin) {
192                                    isValid &= validatePayoutAmount(leavePayout.getPayoutAmount(),fromCat,payoutEarnCode, principalId, effectiveDate);
193                            }
194                            else {
195                                    if(ObjectUtils.isNotNull(pha)) {
196                                            if(ObjectUtils.isNotNull(pha.getLeavePlan())) {
197                                                    AccrualCategoryRule acr = TkServiceLocator.getAccrualCategoryRuleService().getAccrualCategoryRuleForDate(fromCat, effectiveDate, pha.getServiceDate());
198                                                    if(ObjectUtils.isNotNull(acr)) {
199                                                            if(ObjectUtils.isNotNull(acr.getMaxBalFlag())
200                                                                            && StringUtils.isNotBlank(acr.getMaxBalFlag())
201                                                                            && StringUtils.isNotEmpty(acr.getMaxBalFlag())
202                                                                            && StringUtils.equals(acr.getMaxBalFlag(), "Y")) {
203                                                                    if(ObjectUtils.isNotNull(payoutEarnCode)) {
204                                                                            isValid &= validatePrincipal(pha,principalId);
205                                                                            isValid &= validateEffectiveDate(effectiveDate);
206                                                                            isValid &= validateAgainstLeavePlan(pha,fromCat,effectiveDate);
207                                                                            isValid &= validateTransferFromAccrualCategory(fromCat,principalId,effectiveDate,acr);
208                                                                            isValid &= validatePayoutAmount(leavePayout.getPayoutAmount(),fromCat,payoutEarnCode, null, null);
209                                                                            isValid &= isPayoutAmountUnderMaxLimit(principalId, effectiveDate, fromAccrualCategory, leavePayout.getPayoutAmount(), acr, pha.getLeavePlan());
210                                                                    }
211                                                                    else {
212                                                                            //should never be the case if accrual category rules are validated correctly.
213                                                                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory",
214                                                                                            "leavePayout.fromAccrualCategory.rules.payoutToEarnCode",
215                                                                                            fromAccrualCategory);
216                                                                            isValid &= false;
217                                                                    }
218                                                            }
219                                                            else {
220                                                                    //max bal flag null, blank, empty, or "N"
221                                                                    GlobalVariables.getMessageMap().putError("document.newMaintinableObject.fromAccrualCategory",
222                                                                                    "leavePayout.fromAccrualCategory.rules.maxBalFlag", fromAccrualCategory);
223                                                                    isValid &= false;
224                                                            }
225                                                    }
226                                                    else {
227                                                            //department admins must validate amount to transfer does not exceed current balance.
228                                                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.fromAccrualCategory",
229                                                                            "leavePayout.fromAccrualCategory.rules.exist",fromCat.getAccrualCategory());
230                                                            isValid &= false;
231                                                    }
232                                            }
233                                            else {
234                                                    //if the principal doesn't have a leave plan, there aren't any accrual categories that can be debited/credited.
235                                                    GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId","leavePayout.principal.noLeavePlan");
236                                                    isValid &=false;
237                                            }
238                                    }
239                                    else  {
240                                            //if the principal has no principal hr attributes, they're not a principal.
241                                            GlobalVariables.getMessageMap().putError("document.newMaintainableObject.principalId","leavePayout.principal.noAttributes");
242                                            isValid &= false;
243                                    }
244                            }
245                    }
246                    return isValid;
247            }
248    
249    }