001    /*
002     * Copyright 2011 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.kfs.module.ec.document.validation.impl;
017    
018    import static org.kuali.kfs.sys.KFSConstants.CurrencyTypeAmounts.HUNDRED_DOLLAR_AMOUNT;
019    import static org.kuali.kfs.sys.businessobject.AccountingLineOverride.CODE.EXPIRED_ACCOUNT;
020    import static org.kuali.kfs.sys.businessobject.AccountingLineOverride.CODE.EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED;
021    
022    import java.util.Arrays;
023    import java.util.List;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.coa.businessobject.A21SubAccount;
027    import org.kuali.kfs.coa.businessobject.Account;
028    import org.kuali.kfs.module.ec.EffortConstants;
029    import org.kuali.kfs.module.ec.businessobject.EffortCertificationDetail;
030    import org.kuali.kfs.module.ec.document.EffortCertificationDocument;
031    import org.kuali.kfs.module.ec.util.PayrollAmountHolder;
032    import org.kuali.kfs.sys.KFSConstants;
033    import org.kuali.kfs.sys.ObjectUtil;
034    import org.kuali.kfs.sys.context.SpringContext;
035    import org.kuali.kfs.sys.service.UniversityDateService;
036    import org.kuali.rice.kns.service.DictionaryValidationService;
037    import org.kuali.rice.kns.util.GlobalVariables;
038    import org.kuali.rice.kns.util.KualiDecimal;
039    import org.kuali.rice.kns.util.ObjectUtils;
040    
041    /**
042     * Provides a set of facilities to determine whether the given Effort Certification Documents or Effort Certification Detail meet
043     * the specified requirements.
044     */
045    public class EffortCertificationDocumentRuleUtil {
046        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EffortCertificationDocumentRuleUtil.class);
047    
048        /**
049         * reset the attribute with the blank value to the default values
050         * 
051         * @param detailLine the given detail line
052         */
053        public static void applyDefaultValues(EffortCertificationDetail detailLine) {        
054          
055           if (StringUtils.isBlank(detailLine.getSubAccountNumber())) {
056               detailLine.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
057           }
058            
059            if (StringUtils.isBlank(detailLine.getCostShareSourceSubAccountNumber())) {
060               detailLine.setCostShareSourceSubAccountNumber(KFSConstants.getDashSubAccountNumber());
061            }
062    
063            if (StringUtils.isBlank(detailLine.getSourceChartOfAccountsCode())) {
064                detailLine.setSourceChartOfAccountsCode(EffortConstants.DASH_CHART_OF_ACCOUNTS_CODE);
065            }
066    
067            if (StringUtils.isBlank(detailLine.getSourceAccountNumber())) {
068                detailLine.setSourceAccountNumber(EffortConstants.DASH_ACCOUNT_NUMBER);
069            }
070    
071            if (ObjectUtils.isNull(detailLine.getEffortCertificationPayrollAmount())) {
072                detailLine.setEffortCertificationPayrollAmount(KualiDecimal.ZERO);
073            }
074    
075            if (ObjectUtils.isNull(detailLine.getEffortCertificationOriginalPayrollAmount())) {
076                detailLine.setEffortCertificationOriginalPayrollAmount(KualiDecimal.ZERO);
077            }
078    
079            if (ObjectUtils.isNull(detailLine.getEffortCertificationCalculatedOverallPercent())) {
080                detailLine.setEffortCertificationCalculatedOverallPercent(0);
081            }
082    
083            if (ObjectUtils.isNull(detailLine.getEffortCertificationUpdatedOverallPercent())) {
084                detailLine.setEffortCertificationUpdatedOverallPercent(0);
085            }
086    
087            UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class);
088            detailLine.setUniversityFiscalYear(universityDateService.getCurrentFiscalYear());
089        }
090    
091        /**
092         * determine whether the expired account in the detail line can be used.
093         * 
094         * @param detailLine the given detail line
095         * @return true if the expired account in the detail line can be used; otherwise, false
096         */
097        public static boolean canExpiredAccountBeUsed(EffortCertificationDetail detailLine) {
098            Account account = detailLine.getAccount();
099    
100            boolean canExpiredAccountUsed = true;
101            if (ObjectUtils.isNotNull(account) && account.isExpired()) {
102                String overrideCode = detailLine.getOverrideCode();
103                canExpiredAccountUsed = Arrays.asList(EXPIRED_ACCOUNT, EXPIRED_ACCOUNT_AND_NON_FRINGE_ACCOUNT_USED).contains(overrideCode);
104            }
105            return canExpiredAccountUsed;
106        }
107    
108        /**
109         * determine if the sub account associated with the given detail line is a valid A21 sub account
110         * 
111         * @param detailLine the given detail line
112         * @return true if the sub account associated with the given detail line is a valid A21 sub account; otherwise, false
113         */
114        public static boolean hasA21SubAccount(EffortCertificationDetail detailLine) {
115            String subAccountNumber = detailLine.getSubAccountNumber();
116            if (KFSConstants.getDashSubAccountNumber().equals(subAccountNumber)) {
117                return false;
118            }
119           return ObjectUtils.isNotNull(detailLine.getSubAccount().getA21SubAccount());
120        }
121    
122        /**
123         * determine if the given detail line is associated with a closed account
124         * 
125         * @param detailLine the given detail line
126         * @return true if the given detail line is associated with a closed account; otherwise, false
127         */
128        public static boolean hasClosedAccount(EffortCertificationDetail detailLine) {
129            return !detailLine.getAccount().isActive();
130        }
131    
132        /**
133         * determine if the given detail line is associated with a contract grant account
134         * 
135         * @param detailLine the given detail line
136         * @return true if the given detail line is associated with a contract grant account; otherwise, false
137         */
138        public static boolean hasContractGrantAccount(EffortCertificationDetail detailLine) {
139            return detailLine.getAccount().isForContractsAndGrants();
140        }
141    
142        /**
143         * determine if the given detail line is associated with a sub account whose type code is in the given list
144         * 
145         * @param detailLine the given detail line
146         * @param designatedCostShareSubAccountTypeCode the designated cost share sub account type codes
147         * @return true if the given detail line is associated with a sub account whose type code is in the given list; otherwise, false
148         */
149        public static boolean hasCostShareSubAccount(EffortCertificationDetail detailLine, List<String> designatedCostShareSubAccountTypeCodes) {
150            if (!hasA21SubAccount(detailLine)) {
151                return false;
152            }
153    
154            String costShareSubAccountTypeCode = detailLine.getSubAccount().getA21SubAccount().getSubAccountTypeCode();
155            return designatedCostShareSubAccountTypeCodes.contains(costShareSubAccountTypeCode);
156        }
157    
158        /**
159         * determine if the payroll amount of the given detail line is not negative
160         * 
161         * @param detailLine the given detail line
162         * @return true if the payroll amount of the given detail line is not negative; otherwise, false
163         */
164        public static boolean hasNonnegativePayrollAmount(EffortCertificationDetail detailLine) {
165            KualiDecimal payrollAmount = detailLine.getEffortCertificationPayrollAmount();
166            
167            return ObjectUtils.isNotNull(payrollAmount) && isPayrollAmountNonnegative(payrollAmount);
168        }
169    
170        /**
171         * determine if there is a line in the given document that has the same values for the comparable fields as the given detail
172         * line
173         * 
174         * @param document the given effort certification document
175         * @param detailLine the given detail line
176         * @param comparableFields the comparable fields
177         * @return true if there is a line in the given document that has the same values for the comparable fields as the given detail
178         *         line; otherwise, false
179         */
180        public static boolean hasSameExistingLine(EffortCertificationDocument document, EffortCertificationDetail detailLine, List<String> comparableFields) {
181            List<EffortCertificationDetail> detailLines = document.getEffortCertificationDetailLines();
182    
183            for (EffortCertificationDetail line : detailLines) {
184                if(detailLine != line && ObjectUtil.equals(line, detailLine, comparableFields)) {
185                    return true;
186                }
187            }        
188            return false;
189        }
190    
191        /**
192         * determine if the given detail line has a valid effort percentage. The percentage should be between 0 and 100.
193         * 
194         * @param detailLine the given detail line
195         * @return true if the given detail line has a valid effort percentage; otherwise, false
196         */
197        public static boolean hasValidEffortPercent(EffortCertificationDetail detailLine) {
198            Integer effortPercent = detailLine.getEffortCertificationUpdatedOverallPercent();
199            
200            return ObjectUtils.isNotNull(effortPercent) && isValidPercent(effortPercent);
201        }
202    
203        /**
204         * determine if the fields in the detail line are in the correct formats defined in the data dictionary
205         * 
206         * @param detailLine the given detail line
207         * @return true if the fields in the detail line are in the correct formats defined in the data dictionary; otherwise, false
208         */
209        public static boolean hasValidFormat(EffortCertificationDetail detailLine) {
210            int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
211            SpringContext.getBean(DictionaryValidationService.class).validateBusinessObject(detailLine);
212            int currentErrorCount = GlobalVariables.getMessageMap().getErrorCount();
213    
214            return currentErrorCount == originalErrorCount;
215        }
216    
217        /**
218         * determine if there is a change on the payroll amount of the given detail line comparing to its original payroll amount
219         * 
220         * @param detailLine the given effort certification detail line
221         * @return true if there is a change on the payroll amount of the given detail line comparing to its original payroll amount
222         */
223        public static boolean isPayrollAmountChangedFromOriginal(EffortCertificationDetail detailLine) {
224            KualiDecimal payrollAmount = detailLine.getEffortCertificationPayrollAmount();
225            KualiDecimal originalPayrollAmount = detailLine.getEffortCertificationOriginalPayrollAmount();
226            KualiDecimal difference = originalPayrollAmount.subtract(payrollAmount);
227    
228            return difference.isNonZero();
229        }
230    
231        /**
232         * determine if there is a change on the payroll amount of the given document
233         * 
234         * @param document the given effort certification document
235         * @return true if there is the change on the payroll amount of any detail line in the given document
236         */
237        public static boolean isPayrollAmountChangedFromOriginal(EffortCertificationDocument document) {
238            List<EffortCertificationDetail> detailLines = document.getEffortCertificationDetailLines();
239    
240            for (EffortCertificationDetail line : detailLines) {
241                if (isPayrollAmountChangedFromOriginal(line)) {
242                    return true;
243                }
244            }
245    
246            return false;
247        }
248    
249        /**
250         * determine if there is a change on the payroll amount of the given detail line comparing to its persisted payroll amount
251         * 
252         * @param detailLine the given effort certification detail line
253         * @return true if there is a change on the payroll amount of the given detail line comparing to its persisted payroll amount
254         */
255        public static boolean isPayrollAmountChangedFromPersisted(EffortCertificationDetail detailLine) {
256            KualiDecimal persistedAmount = detailLine.getPersistedPayrollAmount();
257            KualiDecimal difference = KualiDecimal.ZERO;
258    
259            if (ObjectUtils.isNotNull(persistedAmount)) {
260                KualiDecimal payrollAmount = detailLine.getEffortCertificationPayrollAmount();
261                difference = persistedAmount.subtract(payrollAmount);
262            }
263    
264            return difference.isNonZero();
265        }
266        
267        /**
268         * determine if there is a change on the payroll amount of the given document
269         * 
270         * @param document the given effort certification document
271         * @return true if there is the change on the payroll amount of any detail line in the given document
272         */
273        public static boolean isPayrollAmountChangedFromPersisted(EffortCertificationDocument document) {
274            List<EffortCertificationDetail> detailLines = document.getEffortCertificationDetailLines();
275    
276            for (EffortCertificationDetail line : detailLines) {
277                if (isPayrollAmountChangedFromPersisted(line)) {
278                    return true;
279                }
280            }
281    
282            return false;
283        }
284        
285        /**
286         * determine if there is a change on the payroll amount of the given detail line comparing to its persisted payroll amount
287         * 
288         * @param detailLine the given effort certification detail line
289         * @return true if there is a change on the payroll amount of the given detail line comparing to its persisted payroll amount
290         */
291        public static boolean isEffortPercentChangedFromPersisted(EffortCertificationDetail detailLine) {
292            Integer persistedAmount = detailLine.getPersistedEffortPercent();
293            Integer effortPercent = detailLine.getEffortCertificationUpdatedOverallPercent();
294    
295            return !persistedAmount.equals(effortPercent);
296        }
297        
298        /**
299         * determine if there is a change on the payroll amount of the given document
300         * 
301         * @param document the given effort certification document
302         * @return true if there is the change on the payroll amount of any detail line in the given document
303         */
304        public static boolean isEffortPercentChangedFromPersisted(EffortCertificationDocument document) {
305            List<EffortCertificationDetail> detailLines = document.getEffortCertificationDetailLines();
306    
307            for (EffortCertificationDetail line : detailLines) {
308                if (isEffortPercentChangedFromPersisted(line)) {
309                    return true;
310                }
311            }
312    
313            return false;
314        }
315    
316        /**
317         * determine if the given payroll amount is greater than and equal to 0
318         * 
319         * @param payrollAmount the given payroll amount
320         * @return true if the given payroll amount is greater than and equal to 0; otherwise, false
321         */
322        public static boolean isPayrollAmountNonnegative(KualiDecimal payrollAmount) {
323            return payrollAmount.isGreaterEqual(KualiDecimal.ZERO);
324        }
325    
326        /**
327         * determine if original effort percent is same as the current effort percent for the given detail line
328         * @param detailLine the given effort certification detail line
329         * @return true if original effort percent same as current effort percent
330         */
331        
332        public static boolean isOriginalEffortPercentSameAsCurrentEffortPercent(Integer originalEffortPercent, Integer effortPercent) {
333            return originalEffortPercent.equals(effortPercent);
334        }
335        
336        /**
337         * determine if the change on the payroll amount of the given detail line exceeds the specified limit
338         * 
339         * @param detailLine the given effort certification detail line
340         * @param limitOfLinePayrollAmountChange the specified upper bound limit
341         * @return true if the change on the payroll amount of the given detail line exceeds the specified limit; otherwise, false
342         */
343        public static boolean isPayrollAmountOverChanged(EffortCertificationDetail detailLine, KualiDecimal originalTotalAmount, double limitOfLinePayrollAmountChange) {        
344            KualiDecimal payrollAmount = detailLine.getEffortCertificationPayrollAmount();
345            KualiDecimal originalPayrollAmount = detailLine.getEffortCertificationOriginalPayrollAmount();
346            
347            KualiDecimal difference = KualiDecimal.ZERO;
348            
349            Integer originalEffortPercent = detailLine.getEffortCertificationCalculatedOverallPercent();
350            Integer effortPercent = detailLine.getEffortCertificationUpdatedOverallPercent();       
351            if (isOriginalEffortPercentSameAsCurrentEffortPercent(originalEffortPercent, effortPercent)) {
352                difference = originalPayrollAmount.subtract(payrollAmount).multiply(HUNDRED_DOLLAR_AMOUNT).abs();
353                
354                return difference.divide(originalTotalAmount).doubleValue() > limitOfLinePayrollAmountChange * HUNDRED_DOLLAR_AMOUNT.intValue();
355            }
356            
357            return false;
358        }
359    
360        /**
361         * determine if there is a change on the payroll amount of a detail line that exceeds the specified limit
362         * 
363         * @param document the given effort certification document
364         * @param limitOfLinePayrollAmountChange the specified upper bound limit
365         * @return true if the change on the payroll amount of any detail line exceeds the specified limit; otherwise, false
366         */
367        public static boolean isPayrollAmountOverChanged(EffortCertificationDocument document, double limitOfLinePayrollAmountChange) {
368            List<EffortCertificationDetail> detailLines = document.getEffortCertificationDetailLines();
369            KualiDecimal originalTotalAmount = document.getTotalOriginalPayrollAmount();
370    
371            for (EffortCertificationDetail line : detailLines) {
372                if (isPayrollAmountOverChanged(line, originalTotalAmount, limitOfLinePayrollAmountChange)) {
373                    return true;
374                }
375            }
376    
377            return false;
378        }
379    
380        /**
381         * detrmine if the total effort percent of the given document is 100
382         * 
383         * @param document the given effort certification document
384         * @return true if the total effort percent of the given document is 100
385         */
386        public static boolean isTotalEffortPercentageAs100(EffortCertificationDocument document) {
387            return document.getTotalEffortPercent() == 100;
388        }
389    
390        /**
391         * determine if the change on the total payroll amount exceeds the specified limit
392         * 
393         * @param document the given effort certification document
394         * @param limitOfTotalPayrollAmountChange the specified upper bound limit
395         * @return true if the change on the total payroll amount exceeds the specified limit; otherwise, false
396         */
397        public static boolean isTotalPayrollAmountOverChanged(EffortCertificationDocument document, double limitOfTotalPayrollAmountChange) {
398            KualiDecimal totalPayrollAmount = document.getTotalPayrollAmount();
399            KualiDecimal totalOriginalPayrollAmount = document.getTotalOriginalPayrollAmount();
400            KualiDecimal difference = totalOriginalPayrollAmount.subtract(totalPayrollAmount).abs();
401    
402            return difference.doubleValue() > limitOfTotalPayrollAmountChange;
403        }
404    
405        /**
406         * determine if the given percent is between 0 and 100.
407         * 
408         * @param percent the given percent
409         * @return true if the given percent is between 0 and 100; otherwise, false
410         */
411        public static boolean isValidPercent(Integer percent) {
412            return percent >= 0 && percent <= 100;
413        }
414    
415        /**
416         * update the information of the source attributes for the given detail line
417         * 
418         * @param detailLine the given detail line
419         */
420        public static void updateSourceAccountInformation(EffortCertificationDetail detailLine) {
421            A21SubAccount a21SubAccount = detailLine.getSubAccount().getA21SubAccount();
422    
423            if (ObjectUtils.isNotNull(a21SubAccount)) {
424                detailLine.setSourceChartOfAccountsCode(a21SubAccount.getCostShareChartOfAccountCode());
425                detailLine.setSourceAccountNumber(a21SubAccount.getCostShareSourceAccountNumber());
426                detailLine.setCostShareSourceSubAccountNumber(a21SubAccount.getCostShareSourceSubAccountNumber());
427            }
428        }
429        
430        /**
431         * determine if there is a line associated with the given document
432         * 
433         * @param document the given effort certification document
434         * @return true if there is a line associated with the given document; otherwise, false
435         */
436        public static boolean hasDetailLine(EffortCertificationDocument document) {;
437            List<EffortCertificationDetail> detailLines = document.getEffortCertificationDetailLines();
438    
439            return detailLines != null && !detailLines.isEmpty();
440        }
441    
442    }