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.ld.document.validation.impl;
017    
018    import java.util.ArrayList;
019    import java.util.HashMap;
020    import java.util.List;
021    import java.util.Map;
022    import java.util.Set;
023    import java.util.Map.Entry;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.module.ld.LaborKeyConstants;
027    import org.kuali.kfs.module.ld.LaborPropertyConstants;
028    import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine;
029    import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine;
030    import org.kuali.kfs.module.ld.businessobject.LedgerBalance;
031    import org.kuali.kfs.module.ld.document.LaborExpenseTransferDocumentBase;
032    import org.kuali.kfs.sys.KFSConstants;
033    import org.kuali.kfs.sys.KFSPropertyConstants;
034    import org.kuali.kfs.sys.ObjectUtil;
035    import org.kuali.kfs.sys.businessobject.SystemOptions;
036    import org.kuali.kfs.sys.context.SpringContext;
037    import org.kuali.kfs.sys.document.validation.GenericValidation;
038    import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
039    import org.kuali.kfs.sys.service.OptionsService;
040    import org.kuali.rice.kns.document.Document;
041    import org.kuali.rice.kns.service.BusinessObjectService;
042    import org.kuali.rice.kns.util.GlobalVariables;
043    import org.kuali.rice.kns.util.KualiDecimal;
044    import org.kuali.kfs.module.ld.document.BenefitExpenseTransferDocument;
045    import org.kuali.kfs.module.ld.util.ConsolidationUtil;
046    /**
047     * check to ensure totals of accounting lines in source and target sections match by pay FY + pay period
048     * 
049     * @param accountingDocument the given document
050     * @return true if the given accounting lines in source and target match by pay fy and pp
051     */
052    public class LaborExpenseTransferValidTransferAmountValidation extends GenericValidation {
053        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborExpenseTransferValidTransferAmountValidation.class);
054        
055        private Document documentForValidation;  
056        
057        /**
058         * Validates before the document routes 
059         * @see org.kuali.kfs.validation.Validation#validate(java.lang.Object[])
060         */
061        public boolean validate(AttributedDocumentEvent event) {
062            boolean result = true;
063                   
064            Document documentForValidation = getDocumentForValidation();
065    
066            LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) documentForValidation;
067            
068            List sourceLines = expenseTransferDocument.getSourceAccountingLines();
069    
070            Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = this.getAccountingLineGroupMap(sourceLines, ExpenseTransferSourceAccountingLine.class);
071    
072            boolean isValidTransferAmount = isValidTransferAmount(accountingLineGroupMap);
073            if (!isValidTransferAmount) {
074                GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, LaborKeyConstants.ERROR_TRANSFER_AMOUNT_EXCEED_MAXIMUM);
075                return false;
076            }
077                    
078            return result;       
079        }
080    
081        /**
082         * determine whether the amount to be tranferred is only up to the amount in ledger balance for a given pay period
083         * 
084         * @param accountingDocument the given accounting document
085         * @return true if the amount to be tranferred is only up to the amount in ledger balance for a given pay period; otherwise,
086         *         false
087         */
088        protected boolean isValidTransferAmount(Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap) {
089            Set<Entry<String, ExpenseTransferAccountingLine>> entrySet = accountingLineGroupMap.entrySet();
090    
091            for (Entry<String, ExpenseTransferAccountingLine> entry : entrySet) {
092                ExpenseTransferAccountingLine accountingLine = entry.getValue();
093                Map<String, Object> fieldValues = this.buildFieldValueMap(accountingLine);
094    
095                KualiDecimal balanceAmount = getBalanceAmount(fieldValues, accountingLine.getPayrollEndDateFiscalPeriodCode());
096                KualiDecimal transferAmount = accountingLine.getAmount();
097    
098                // the tranferred amount cannot greater than the balance amount
099                if (balanceAmount.abs().isLessThan(transferAmount.abs())) {
100                    return false;
101                }
102    
103                // a positive amount cannot be transferred if the balance amount is negative
104                if (balanceAmount.isNegative() && transferAmount.isPositive()) {
105                    return false;
106                }
107            }
108            return true;
109        }
110        /**
111         * build the field-value maps throught the given accouting line
112         * 
113         * @param accountingLine the given accounting line
114         * @return the field-value maps built from the given accouting line
115         */
116        protected Map<String, Object> buildFieldValueMap(ExpenseTransferAccountingLine accountingLine) {
117            Map<String, Object> fieldValues = new HashMap<String, Object>();
118    
119            fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, accountingLine.getPayrollEndDateFiscalYear());
120            fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, accountingLine.getChartOfAccountsCode());
121            fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountingLine.getAccountNumber());
122    
123            String subAccountNumber = accountingLine.getSubAccountNumber();
124            subAccountNumber = StringUtils.isBlank(subAccountNumber) ? KFSConstants.getDashSubAccountNumber() : subAccountNumber;
125            fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, subAccountNumber);
126    
127            fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, accountingLine.getBalanceTypeCode());
128            fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, accountingLine.getFinancialObjectCode());
129    
130            SystemOptions options = SpringContext.getBean(OptionsService.class).getOptions(accountingLine.getPayrollEndDateFiscalYear());
131            fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE, options.getFinObjTypeExpenditureexpCd());
132    
133            String subObjectCode = accountingLine.getFinancialSubObjectCode();
134            subObjectCode = StringUtils.isBlank(subObjectCode) ? KFSConstants.getDashFinancialSubObjectCode() : subObjectCode;
135            fieldValues.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE, subObjectCode);
136    
137            fieldValues.put(KFSPropertyConstants.EMPLID, accountingLine.getEmplid());
138            fieldValues.put(KFSPropertyConstants.POSITION_NUMBER, accountingLine.getPositionNumber());
139    
140            Document documentForValidation = getDocumentForValidation();    
141            if (documentForValidation instanceof BenefitExpenseTransferDocument) {
142                fieldValues.remove(KFSPropertyConstants.EMPLID);
143                fieldValues.remove(KFSPropertyConstants.POSITION_NUMBER);            
144            }
145            return fieldValues;
146        }
147        
148        /**
149         * Groups the accounting lines by the specified key fields
150         * 
151         * @param accountingLines the given accounting lines that are stored in a list
152         * @param clazz the class type of given accounting lines
153         * @return the accounting line groups
154         */
155        protected Map<String, ExpenseTransferAccountingLine> getAccountingLineGroupMap(List<ExpenseTransferAccountingLine> accountingLines, Class clazz) {
156            Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = new HashMap<String, ExpenseTransferAccountingLine>();
157    
158            for (ExpenseTransferAccountingLine accountingLine : accountingLines) {
159                String stringKey = ObjectUtil.buildPropertyMap(accountingLine, defaultKeyOfExpenseTransferAccountingLine()).toString();
160                ExpenseTransferAccountingLine line = null;
161    
162                if (accountingLineGroupMap.containsKey(stringKey)) {
163                    line = accountingLineGroupMap.get(stringKey);
164                    KualiDecimal amount = line.getAmount();
165                    line.setAmount(amount.add(accountingLine.getAmount()));
166                }
167                else {
168                    try {
169                        line = (ExpenseTransferAccountingLine) clazz.newInstance();
170                        ObjectUtil.buildObject(line, accountingLine);
171                        accountingLineGroupMap.put(stringKey, line);
172                    }
173                    catch (Exception e) {
174                        LOG.error("Cannot create a new instance of ExpenseTransferAccountingLine" + e);
175                    }
176                }
177            }
178            return accountingLineGroupMap;
179        }
180        
181        /**
182         * Gets the default key of ExpenseTransferAccountingLine
183         * 
184         * @return the default key of ExpenseTransferAccountingLine
185         */
186        protected List<String> defaultKeyOfExpenseTransferAccountingLine() {
187            List<String> defaultKey = new ArrayList<String>();
188    
189            defaultKey.add(KFSPropertyConstants.POSTING_YEAR);
190            defaultKey.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
191            defaultKey.add(KFSPropertyConstants.ACCOUNT_NUMBER);
192            defaultKey.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER);
193    
194            defaultKey.add(KFSPropertyConstants.BALANCE_TYPE_CODE);
195            defaultKey.add(KFSPropertyConstants.FINANCIAL_OBJECT_CODE);
196            defaultKey.add(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE);
197    
198            defaultKey.add(KFSPropertyConstants.EMPLID);
199            defaultKey.add(KFSPropertyConstants.POSITION_NUMBER);
200    
201            defaultKey.add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_YEAR);
202            defaultKey.add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_PERIOD_CODE);
203    
204            return defaultKey;
205        }
206        
207        /**
208         * get the amount for a given period from a ledger balance that has the given values for specified fileds
209         * 
210         * @param fieldValues the given fields and their values
211         * @param periodCode the given period
212         * @return the amount for a given period from the qualified ledger balance
213         */
214        protected KualiDecimal getBalanceAmount(Map<String, Object> fieldValues, String periodCode) {
215            if (periodCode == null) {
216                return KualiDecimal.ZERO;
217            }
218    
219            fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_ACTUAL);
220            KualiDecimal actualBalanceAmount = this.getBalanceAmountOfGivenPeriod(fieldValues, periodCode);
221    
222            fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_A21);
223            KualiDecimal effortBalanceAmount = this.getBalanceAmountOfGivenPeriod(fieldValues, periodCode);
224    
225            return actualBalanceAmount.add(effortBalanceAmount);
226        }
227    
228        /**
229         * Gets the balance amount of a given period
230         * 
231         * @param fieldValues
232         * @param periodCode
233         * @return
234         */
235        protected KualiDecimal getBalanceAmountOfGivenPeriod(Map<String, Object> fieldValues, String periodCode) {
236            KualiDecimal balanceAmount = KualiDecimal.ZERO;
237            List<LedgerBalance> ledgerBalances = (List<LedgerBalance>) SpringContext.getBean(BusinessObjectService.class).findMatching(LedgerBalance.class, fieldValues);
238            
239            LedgerBalance summaryBalance = new LedgerBalance();
240            for(LedgerBalance balance : ledgerBalances) {
241                ConsolidationUtil.sumLedgerBalances(summaryBalance, balance);
242            }
243        
244            return summaryBalance.getAmount(periodCode);
245        }
246    
247        /**
248         * Gets the documentForValidation attribute. 
249         * @return Returns the documentForValidation.
250         */
251        public Document getDocumentForValidation() {
252            return documentForValidation;
253        }
254    
255        /**
256         * Sets the accountingDocumentForValidation attribute value.
257         * @param documentForValidation The documentForValidation to set.
258         */
259        public void setDocumentForValidation(Document documentForValidation) {
260            this.documentForValidation = documentForValidation;
261        }    
262    }