/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.ld.document.validation.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.module.ld.LaborKeyConstants; import org.kuali.kfs.module.ld.LaborPropertyConstants; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferAccountingLine; import org.kuali.kfs.module.ld.businessobject.ExpenseTransferSourceAccountingLine; import org.kuali.kfs.module.ld.businessobject.LedgerBalance; import org.kuali.kfs.module.ld.document.LaborExpenseTransferDocumentBase; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.ObjectUtil; import org.kuali.kfs.sys.businessobject.SystemOptions; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.validation.GenericValidation; import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent; import org.kuali.kfs.sys.service.OptionsService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.util.GlobalVariables; /** * Determines whether a negtive amount can be transferred from one account to another * * @param document the given document * @return true Determines whether a negtive amount can be transferred from one account to another */ public class LaborExpenseTransferNegtiveAmountBeTransferredValidation extends GenericValidation { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(LaborExpenseTransferNegtiveAmountBeTransferredValidation.class); private Document documentForValidation; /** * Validates before the document routes * @see org.kuali.kfs.validation.Validation#validate(java.lang.Object[]) */ public boolean validate(AttributedDocumentEvent event) { boolean result = true; Document documentForValidation = getDocumentForValidation(); LaborExpenseTransferDocumentBase expenseTransferDocument = (LaborExpenseTransferDocumentBase) documentForValidation; List sourceLines = expenseTransferDocument.getSourceAccountingLines(); // allow a negative amount to be moved from one account to another but do not allow a negative amount to be created when the // balance is positive Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = this.getAccountingLineGroupMap(sourceLines, ExpenseTransferSourceAccountingLine.class); if (result) { boolean canNegtiveAmountBeTransferred = canNegtiveAmountBeTransferred(accountingLineGroupMap); if (!canNegtiveAmountBeTransferred) { GlobalVariables.getMessageMap().putError(KFSPropertyConstants.SOURCE_ACCOUNTING_LINES, LaborKeyConstants.ERROR_CANNOT_TRANSFER_NEGATIVE_AMOUNT); result = false; } } return result; } /** * Determines whether a negtive amount can be transferred from one account to another * * @param accountingLineGroupMap the givenaccountingLineGroupMap * @return true if a negtive amount can be transferred from one account to another; otherwise, false */ protected boolean canNegtiveAmountBeTransferred(Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap) { for (String key : accountingLineGroupMap.keySet()) { ExpenseTransferAccountingLine accountingLine = accountingLineGroupMap.get(key); Map<String, Object> fieldValues = this.buildFieldValueMap(accountingLine); KualiDecimal balanceAmount = getBalanceAmount(fieldValues, accountingLine.getPayrollEndDateFiscalPeriodCode()); KualiDecimal transferAmount = accountingLine.getAmount(); // a negtive amount cannot be transferred if the balance amount is positive if (transferAmount.isNegative() && balanceAmount.isPositive()) { return false; } } return true; } /** * build the field-value maps throught the given accouting line * * @param accountingLine the given accounting line * @return the field-value maps built from the given accouting line */ protected Map<String, Object> buildFieldValueMap(ExpenseTransferAccountingLine accountingLine) { Map<String, Object> fieldValues = new HashMap<String, Object>(); fieldValues.put(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, accountingLine.getPostingYear()); fieldValues.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, accountingLine.getChartOfAccountsCode()); fieldValues.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountingLine.getAccountNumber()); String subAccountNumber = accountingLine.getSubAccountNumber(); subAccountNumber = StringUtils.isBlank(subAccountNumber) ? KFSConstants.getDashSubAccountNumber() : subAccountNumber; fieldValues.put(KFSPropertyConstants.SUB_ACCOUNT_NUMBER, subAccountNumber); fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, accountingLine.getBalanceTypeCode()); fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_CODE, accountingLine.getFinancialObjectCode()); SystemOptions options = SpringContext.getBean(OptionsService.class).getOptions(accountingLine.getPostingYear()); fieldValues.put(KFSPropertyConstants.FINANCIAL_OBJECT_TYPE_CODE, options.getFinObjTypeExpenditureexpCd()); String subObjectCode = accountingLine.getFinancialSubObjectCode(); subObjectCode = StringUtils.isBlank(subObjectCode) ? KFSConstants.getDashFinancialSubObjectCode() : subObjectCode; fieldValues.put(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE, subObjectCode); fieldValues.put(KFSPropertyConstants.EMPLID, accountingLine.getEmplid()); fieldValues.put(KFSPropertyConstants.POSITION_NUMBER, accountingLine.getPositionNumber()); return fieldValues; } /** * Groups the accounting lines by the specified key fields * * @param accountingLines the given accounting lines that are stored in a list * @param clazz the class type of given accounting lines * @return the accounting line groups */ protected Map<String, ExpenseTransferAccountingLine> getAccountingLineGroupMap(List<ExpenseTransferAccountingLine> accountingLines, Class clazz) { Map<String, ExpenseTransferAccountingLine> accountingLineGroupMap = new HashMap<String, ExpenseTransferAccountingLine>(); for (ExpenseTransferAccountingLine accountingLine : accountingLines) { String stringKey = ObjectUtil.buildPropertyMap(accountingLine, defaultKeyOfExpenseTransferAccountingLine()).toString(); ExpenseTransferAccountingLine line = null; if (accountingLineGroupMap.containsKey(stringKey)) { line = accountingLineGroupMap.get(stringKey); KualiDecimal amount = line.getAmount(); line.setAmount(amount.add(accountingLine.getAmount())); } else { try { line = (ExpenseTransferAccountingLine) clazz.newInstance(); ObjectUtil.buildObject(line, accountingLine); accountingLineGroupMap.put(stringKey, line); } catch (Exception e) { LOG.error("Cannot create a new instance of ExpenseTransferAccountingLine" + e); } } } return accountingLineGroupMap; } /** * Gets the default key of ExpenseTransferAccountingLine * * @return the default key of ExpenseTransferAccountingLine */ protected List<String> defaultKeyOfExpenseTransferAccountingLine() { List<String> defaultKey = new ArrayList<String>(); defaultKey.add(KFSPropertyConstants.POSTING_YEAR); defaultKey.add(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE); defaultKey.add(KFSPropertyConstants.ACCOUNT_NUMBER); defaultKey.add(KFSPropertyConstants.SUB_ACCOUNT_NUMBER); defaultKey.add(KFSPropertyConstants.BALANCE_TYPE_CODE); defaultKey.add(KFSPropertyConstants.FINANCIAL_OBJECT_CODE); defaultKey.add(KFSPropertyConstants.FINANCIAL_SUB_OBJECT_CODE); defaultKey.add(KFSPropertyConstants.EMPLID); defaultKey.add(KFSPropertyConstants.POSITION_NUMBER); defaultKey.add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_YEAR); defaultKey.add(LaborPropertyConstants.PAYROLL_END_DATE_FISCAL_PERIOD_CODE); return defaultKey; } /** * get the amount for a given period from a ledger balance that has the given values for specified fileds * * @param fieldValues the given fields and their values * @param periodCode the given period * @return the amount for a given period from the qualified ledger balance */ protected KualiDecimal getBalanceAmount(Map<String, Object> fieldValues, String periodCode) { if (periodCode == null) { return KualiDecimal.ZERO; } fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_ACTUAL); KualiDecimal actualBalanceAmount = this.getBalanceAmountOfGivenPeriod(fieldValues, periodCode); fieldValues.put(KFSPropertyConstants.FINANCIAL_BALANCE_TYPE_CODE, KFSConstants.BALANCE_TYPE_A21); KualiDecimal effortBalanceAmount = this.getBalanceAmountOfGivenPeriod(fieldValues, periodCode); return actualBalanceAmount.add(effortBalanceAmount); } /** * Gets the balance amount of a given period * * @param fieldValues * @param periodCode * @return */ protected KualiDecimal getBalanceAmountOfGivenPeriod(Map<String, Object> fieldValues, String periodCode) { KualiDecimal balanceAmount = KualiDecimal.ZERO; List<LedgerBalance> ledgerBalances = (List<LedgerBalance>) SpringContext.getBean(BusinessObjectService.class).findMatching(LedgerBalance.class, fieldValues); if (!ledgerBalances.isEmpty()) { balanceAmount = ledgerBalances.get(0).getAmount(periodCode); } return balanceAmount; } /** * Gets the documentForValidation attribute. * @return Returns the documentForValidation. */ public Document getDocumentForValidation() { return documentForValidation; } /** * Sets the accountingDocumentForValidation attribute value. * @param documentForValidation The documentForValidation to set. */ public void setDocumentForValidation(Document documentForValidation) { this.documentForValidation = documentForValidation; } }