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