/*
* 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.fp.document.validation.impl;
import java.util.List;
import org.kuali.kfs.fp.businessobject.CapitalAssetAccountsGroupDetails;
import org.kuali.kfs.fp.businessobject.CapitalAssetInformation;
import org.kuali.kfs.fp.document.CapitalAssetEditable;
import org.kuali.kfs.integration.cab.CapitalAssetBuilderModuleService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.validation.GenericValidation;
import org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* validate the capital asset information associated with the accounting document for validation
*/
public class CapitalAssetInformationValidation extends GenericValidation {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CapitalAssetInformationValidation.class);
private CapitalAssetBuilderModuleService capitalAssetBuilderModuleService = SpringContext.getBean(CapitalAssetBuilderModuleService.class);
private AccountingDocument accountingDocumentForValidation;
/**
* @see org.kuali.kfs.sys.document.validation.Validation#validate(org.kuali.kfs.sys.document.validation.event.AttributedDocumentEvent)
*/
@Override
public boolean validate(AttributedDocumentEvent event) {
boolean valid = true ;
//check if accounting lines have been distributed to capital assets..
valid &= accountingLinesDisributedToCapitalAssets(accountingDocumentForValidation);
if (valid) {
//check if capital assets accounting lines exist in source/target accounting lines...
valid &= capitalAssetsAccountLinesMatchToAccountingLines(accountingDocumentForValidation);
}
if (valid) {
//check if capital assets accounting lines totals match the capital asset amount...
valid &= amountsForCapitalAssetsAndAccountLinesMatch(accountingDocumentForValidation);
}
if (valid) {
//check if distributed accounting lines total matches the capital asset amount...
valid &= amountsForCapitalAssetsAndDistributedAccountLinesMatch(accountingDocumentForValidation);
}
if (valid) {
//make sure capital asset information is valid...
valid &= hasValidCapitalAssetInformation(accountingDocumentForValidation);
}
return valid;
}
/**
* validates that all the accounting lines in source/target section have been
* distributed in the capital assets. Any given accounting line must exist in
* at least one capital asset. Return true if accounting lines exist in capital asset
* else return false.
*
* @param accountingDocument
* @return true if lines have been distributed else false.
*/
protected boolean accountingLinesDisributedToCapitalAssets(AccountingDocument accountingDocument) {
LOG.debug("accountingLinesDisributedToCapitalAssets(accountingDocument) - start");
boolean distributed = true;
if (accountingDocument instanceof CapitalAssetEditable == false) {
return true;
}
CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) accountingDocument;
List<CapitalAssetInformation> capitalAssets = capitalAssetEditable.getCapitalAssetInformation();
List<SourceAccountingLine> sourceAccountLines = accountingDocument.getSourceAccountingLines();
int accountIndex = 0;
for (SourceAccountingLine sourceAccount : sourceAccountLines) {
if (capitalAssetBuilderModuleService.hasCapitalAssetObjectSubType(sourceAccount)) {
//capital object code so we need to check capital asset info...
//check if this sourceAccount does exist in any one capital assets....
if (!checkSourceDistributedAccountingLineExists(sourceAccount, capitalAssets)) {
//account does not exist so put out an error message and get out.
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSPropertyConstants.DOCUMENT + "." + KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS + "[" + accountIndex + "]" + "." + KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_DOCUMENT_SOURCE_ACCOUNTING_LINE_NOT_DISTRIBUTED, sourceAccount.getAccountNumber());
distributed = false;
accountIndex++;
break;
}
}
}
List<TargetAccountingLine> targetAccountLines = accountingDocument.getTargetAccountingLines();
accountIndex = 0;
for (TargetAccountingLine targetAccount : targetAccountLines) {
//check if this targetAccount does exist in any one capital assets....
if (capitalAssetBuilderModuleService.hasCapitalAssetObjectSubType(targetAccount)) {
//capital object code so we need to check capital asset info...
//check if this sourceAccount does exist in any one capital assets....
if (!checkTargetDistributedAccountingLineExists(targetAccount, capitalAssets)) {
//account does not exist so put out an error message and get out.
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSPropertyConstants.DOCUMENT + "." + KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS + "[" + accountIndex + "]" + "." + KFSPropertyConstants.ACCOUNT_NUMBER, KFSKeyConstants.ERROR_DOCUMENT_TARGET_ACCOUNTING_LINE_NOT_DISTRIBUTED, targetAccount.getAccountNumber());
distributed = false;
accountIndex++;
break;
}
}
}
return distributed;
}
/**
* checks source accounting lines again the distributed accounting line and if found
* return true else false so that this distributed accounting line may be removed.
*
* @param accountLine
* @param capitalAssets
* @return true if accounting line exists else return false
*/
protected boolean checkSourceDistributedAccountingLineExists(SourceAccountingLine accountLine, List<CapitalAssetInformation> capitalAssets) {
boolean exists = false;
for (CapitalAssetInformation capitalAsset : capitalAssets) {
for (CapitalAssetAccountsGroupDetails groupAccountLine : capitalAsset.getCapitalAssetAccountsGroupDetails()) {
if (groupAccountLine.getSequenceNumber().compareTo(accountLine.getSequenceNumber()) == 0 &&
groupAccountLine.getFinancialDocumentLineTypeCode().equals(accountLine.getFinancialDocumentLineTypeCode()) &&
groupAccountLine.getChartOfAccountsCode().equals(accountLine.getChartOfAccountsCode()) &&
groupAccountLine.getAccountNumber().equals(accountLine.getAccountNumber()) &&
groupAccountLine.getFinancialObjectCode().equals(accountLine.getFinancialObjectCode())) {
return true;
}
}
}
return exists;
}
/**
* checks target accounting lines again the distributed accounting line and if found
* return true else false so that this distributed accounting line may be removed.
*
* @param accountLine
* @param capitalAssets
* @return true if accounting line exists else return false
*/
protected boolean checkTargetDistributedAccountingLineExists(TargetAccountingLine accountLine, List<CapitalAssetInformation> capitalAssets) {
boolean exists = false;
for (CapitalAssetInformation capitalAsset : capitalAssets) {
for (CapitalAssetAccountsGroupDetails groupAccountLine : capitalAsset.getCapitalAssetAccountsGroupDetails()) {
if (groupAccountLine.getSequenceNumber().compareTo(accountLine.getSequenceNumber()) == 0 &&
groupAccountLine.getFinancialDocumentLineTypeCode().equals(accountLine.getFinancialDocumentLineTypeCode()) &&
groupAccountLine.getChartOfAccountsCode().equals(accountLine.getChartOfAccountsCode()) &&
groupAccountLine.getAccountNumber().equals(accountLine.getAccountNumber()) &&
groupAccountLine.getFinancialObjectCode().equals(accountLine.getFinancialObjectCode())) {
return true;
}
}
}
return exists;
}
// determine whether the given document has valid capital asset information if any
protected boolean hasValidCapitalAssetInformation(AccountingDocument accountingDocument) {
LOG.debug("hasValidCapitalAssetInformation(Document) - start");
boolean isValid = true;
if (accountingDocument instanceof CapitalAssetEditable == false) {
return true;
}
CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) accountingDocument;
List<CapitalAssetInformation> capitalAssets = capitalAssetEditable.getCapitalAssetInformation();
int index = 0;
for (CapitalAssetInformation capitalAssetInformation : capitalAssets) {
if (ObjectUtils.isNotNull(capitalAssetInformation)) {
MessageMap errors = GlobalVariables.getMessageMap();
errors.addToErrorPath(KFSPropertyConstants.DOCUMENT);
String errorPathPrefix = KFSPropertyConstants.CAPITAL_ASSET_INFORMATION + "[" + index + "].";
errors.addToErrorPath(errorPathPrefix);
isValid &= capitalAssetBuilderModuleService.validateFinancialProcessingData(accountingDocument, capitalAssetInformation, index);
errors.removeFromErrorPath(errorPathPrefix);
errors.removeFromErrorPath(KFSPropertyConstants.DOCUMENT);
index++;
}
}
isValid &= capitalAssetBuilderModuleService.validateAssetTags(accountingDocument);
return isValid;
}
/**
* validates that all the accounting lines in capital assets do exist
* source/target accounting line sections. Any given accounting line in the capital asset
* must exist in source/target sections.
* Return true if accounting lines in capital asset exist in source/target accounting lines else
* return false.
*
* @param accountingDocument
* @return true if lines in capital assets exist in source/target accounts else return false.
*/
protected boolean capitalAssetsAccountLinesMatchToAccountingLines(AccountingDocument accountingDocument) {
LOG.debug("capitalAssetsAccountLinesMatchToAccountingLines(accountingDocument) - start");
boolean distributed = true;
if (accountingDocument instanceof CapitalAssetEditable == false) {
return true;
}
CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) accountingDocument;
List<CapitalAssetInformation> capitalAssets = capitalAssetEditable.getCapitalAssetInformation();
int index = 0;
for (CapitalAssetInformation capitalAsset : capitalAssets) {
String errorPathPrefix = KFSPropertyConstants.DOCUMENT + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION + "[" + index + "]." + KFSPropertyConstants.CAPITAL_ASSET_NUMBER;
if (!checkAccountingLineExists(accountingDocument, capitalAsset, errorPathPrefix)) {
// MessageMap errors = GlobalVariables.getMessageMap();
// errors.addToErrorPath(KFSPropertyConstants.DOCUMENT);
// String parentName = (capitalAsset.getCapitalAssetActionIndicator().equalsIgnoreCase(KFSConstants.CapitalAssets.CAPITAL_ASSET_CREATE_ACTION_INDICATOR) ? KFSPropertyConstants.CAPITAL_ASSET_INFORMATION : KFSPropertyConstants.CAPITAL_ASSET_MODIFY_INFORMATION);
// errors.addToErrorPath(parentName);
// String errorPathPrefix = KFSPropertyConstants.CAPITAL_ASSET_INFORMATION + "[" + index + "].";
// errors.addToErrorPath(errorPathPrefix);
//account does not exist so put out an error message and get out.
// GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix, KFSKeyConstants.ERROR_ASSET_ACCOUNT_NUMBER_LINE_NOT_IN_SOURCE_OR_TARGET_ACCOUNTING_LINES, accountNumber);
// errors.removeFromErrorPath(errorPathPrefix);
// errors.removeFromErrorPath(parentName);
// errors.removeFromErrorPath(KFSPropertyConstants.DOCUMENT);
index++;
distributed = false;
break;
}
}
return distributed;
}
/**
* compares the account number from the capital asset accounting lines
* to the source/target accounting lines. If the line does not exist
* then return false, else return true.
*
* @param accountingDocument
* @param capitalAsset
* @return true if capital asset account line exists in
* source/target lines else return false
*/
protected boolean checkAccountingLineExists(AccountingDocument accountingDocument, CapitalAssetInformation capitalAsset, String errorPathPrefix) {
boolean exists = true;
List<CapitalAssetAccountsGroupDetails> groupAccounts = capitalAsset.getCapitalAssetAccountsGroupDetails();
for (CapitalAssetAccountsGroupDetails groupAccount: groupAccounts) {
if (!accountLineExists(accountingDocument, groupAccount)) {
//this account is not found in source/target accounts list...
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix, KFSKeyConstants.ERROR_ASSET_ACCOUNT_NUMBER_LINE_NOT_IN_SOURCE_OR_TARGET_ACCOUNTING_LINES, groupAccount.getAccountNumber());
return false;
}
}
return exists;
}
/**
*
* @param accountingDocument
* @param groupAccount
* @return true if capital asset account exists in source/target lines else return false
*/
protected boolean accountLineExists(AccountingDocument accountingDocument, CapitalAssetAccountsGroupDetails groupAccountLine) {
boolean exists = false;
List<SourceAccountingLine> sourceAccountLines = accountingDocument.getSourceAccountingLines();
for (SourceAccountingLine sourceAccount : sourceAccountLines) {
if (groupAccountLine.getSequenceNumber().compareTo(sourceAccount.getSequenceNumber()) == 0 &&
groupAccountLine.getFinancialDocumentLineTypeCode().equals(sourceAccount.getFinancialDocumentLineTypeCode()) &&
groupAccountLine.getChartOfAccountsCode().equals(sourceAccount.getChartOfAccountsCode()) &&
groupAccountLine.getAccountNumber().equals(sourceAccount.getAccountNumber()) &&
groupAccountLine.getFinancialObjectCode().equals(sourceAccount.getFinancialObjectCode())) {
return true;
}
}
List<TargetAccountingLine> targetAccountLines = accountingDocument.getTargetAccountingLines();
for (TargetAccountingLine targetAccount : targetAccountLines) {
if (groupAccountLine.getSequenceNumber().compareTo(targetAccount.getSequenceNumber()) == 0 &&
groupAccountLine.getFinancialDocumentLineTypeCode().equals(targetAccount.getFinancialDocumentLineTypeCode()) &&
groupAccountLine.getChartOfAccountsCode().equals(targetAccount.getChartOfAccountsCode()) &&
groupAccountLine.getAccountNumber().equals(targetAccount.getAccountNumber()) &&
groupAccountLine.getFinancialObjectCode().equals(targetAccount.getFinancialObjectCode())) {
return true;
}
}
return exists;
}
/**
* total amount in each capital asset is compared to the distributed accounting lines
* and returns true if they are equal, else return false.
*
* @param accountingDocument
* @return true if total amount in capital asset match its distributed accounting lines else
* return false.
*/
protected boolean amountsForCapitalAssetsAndAccountLinesMatch(AccountingDocument accountingDocument) {
LOG.debug("amountsForCapitalAssetsAndAccountLinesMatch(accountingDocument) - start");
boolean amountMatch = true ;
if (accountingDocument instanceof CapitalAssetEditable == false) {
return true;
}
CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) accountingDocument;
List<CapitalAssetInformation> capitalAssets = capitalAssetEditable.getCapitalAssetInformation();
int accountIndex = 0;
List<SourceAccountingLine> sourceAccountLines = accountingDocument.getSourceAccountingLines();
for (SourceAccountingLine sourceAccount : sourceAccountLines) {
if (capitalAssetBuilderModuleService.hasCapitalAssetObjectSubType(sourceAccount)) {
//capital object code so we need to check capital asset info...
//check if this sourceAccount amount match from accounting lines in capital assets....
KualiDecimal distributedAmount = getSourceDistributedTotalAmount(sourceAccount, capitalAssets);
//if the amounts to not match then do not proceed further...
if (sourceAccount.getAmount().compareTo(distributedAmount) != 0) {
//account does not exist so put out an error message and get out.
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSPropertyConstants.DOCUMENT + "." + KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS + "[" + accountIndex + "]" + "." + KFSPropertyConstants.AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_SOURCE_ACCOUNTING_LINE_AMOUNT_NOT_DISTRIBUTED, sourceAccount.getAccountNumber());
amountMatch = false;
accountIndex++;
break;
}
}
}
accountIndex = 0;
List<TargetAccountingLine> targetAccountLines = accountingDocument.getTargetAccountingLines();
for (TargetAccountingLine targetAccount : targetAccountLines) {
//check if this targetAccount does exist in any one capital assets....
if (capitalAssetBuilderModuleService.hasCapitalAssetObjectSubType(targetAccount)) {
//capital object code so we need to check capital asset info...
//check if this sourceAccount amount match from accounting lines in capital assets....
KualiDecimal distributedAmount = getTargetDistributedTotalAmount(targetAccount, capitalAssets);
//if the amounts to not match then do not proceed further...
if (targetAccount.getAmount().compareTo(distributedAmount) != 0) {
//account does not exist so put out an error message and get out.
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(KFSPropertyConstants.DOCUMENT + "." + KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS + "[" + accountIndex + "]" + "." + KFSPropertyConstants.AMOUNT, KFSKeyConstants.ERROR_DOCUMENT_TARGET_ACCOUNTING_LINE_AMOUNT_NOT_DISTRIBUTED, targetAccount.getAccountNumber());
amountMatch = false;
accountIndex++;
break;
}
}
}
return amountMatch;
}
/**
* checks amount from source accounting line to that of all distributed accounting line
* from capital assets and return true if matched else false.
*
* @param accountLine
* @param capitalAssets
* @return true if amount match to distributed accounting lines in capital assets
* else return false
*/
protected KualiDecimal getSourceDistributedTotalAmount(SourceAccountingLine accountLine, List<CapitalAssetInformation> capitalAssets) {
KualiDecimal amount = new KualiDecimal(0);
for (CapitalAssetInformation capitalAsset : capitalAssets) {
for (CapitalAssetAccountsGroupDetails groupAccountLine : capitalAsset.getCapitalAssetAccountsGroupDetails()) {
if (groupAccountLine.getSequenceNumber().compareTo(accountLine.getSequenceNumber()) == 0 &&
groupAccountLine.getFinancialDocumentLineTypeCode().equals(accountLine.getFinancialDocumentLineTypeCode()) &&
groupAccountLine.getChartOfAccountsCode().equals(accountLine.getChartOfAccountsCode()) &&
groupAccountLine.getAccountNumber().equals(accountLine.getAccountNumber()) &&
groupAccountLine.getFinancialObjectCode().equals(accountLine.getFinancialObjectCode())) {
amount = amount.add(groupAccountLine.getAmount());
}
}
}
return amount;
}
/**
* checks amount from target accounting line to that of all distributed accounting line
* from capital assets and return true if matched else false.
*
* @param accountLine
* @param capitalAssets
* @return true if amount match to distributed accounting lines in capital assets
* else return false
*/
protected KualiDecimal getTargetDistributedTotalAmount(TargetAccountingLine accountLine, List<CapitalAssetInformation> capitalAssets) {
KualiDecimal amount = new KualiDecimal(0);
for (CapitalAssetInformation capitalAsset : capitalAssets) {
for (CapitalAssetAccountsGroupDetails groupAccountLine : capitalAsset.getCapitalAssetAccountsGroupDetails()) {
if (groupAccountLine.getSequenceNumber().compareTo(accountLine.getSequenceNumber()) == 0 &&
groupAccountLine.getFinancialDocumentLineTypeCode().equals(accountLine.getFinancialDocumentLineTypeCode()) &&
groupAccountLine.getChartOfAccountsCode().equals(accountLine.getChartOfAccountsCode()) &&
groupAccountLine.getAccountNumber().equals(accountLine.getAccountNumber()) &&
groupAccountLine.getFinancialObjectCode().equals(accountLine.getFinancialObjectCode())) {
amount = amount.add(groupAccountLine.getAmount());
}
}
}
return amount;
}
/**
* compares each capital asset amount to its distributed accounting lines. If they match
* return true else false
*
* @param accountingDocument
* @return true if capital asset amount match to its distributed accounting lines
* else return false
*/
protected boolean amountsForCapitalAssetsAndDistributedAccountLinesMatch(AccountingDocument accountingDocument) {
LOG.debug("amountsForCapitalAssetsAndDistributedAccountLinesMatch(accountingDocument) - start");
boolean amountMatch = true;
if (accountingDocument instanceof CapitalAssetEditable == false) {
return true;
}
CapitalAssetEditable capitalAssetEditable = (CapitalAssetEditable) accountingDocument;
List<CapitalAssetInformation> capitalAssets = capitalAssetEditable.getCapitalAssetInformation();
int index = 0;
for (CapitalAssetInformation capitalAsset : capitalAssets) {
String errorPathPrefix = KFSPropertyConstants.DOCUMENT + "." + KFSPropertyConstants.CAPITAL_ASSET_INFORMATION + "[" + index + "]." + KFSPropertyConstants.CAPITAL_ASSET_NUMBER;
if (!checkAmount(capitalAsset, errorPathPrefix)) {
index++;
amountMatch = false;
break;
}
}
return amountMatch;
}
/**
* compares the capital asset amount to this accounting lines and if they match return
* true else return false
* to the source/target accounting lines. If the line does not exist
* then return false, else return true.
*
* @param capitalAsset
* @return true if capital asset account line exists in
* source/target lines else return false
*/
protected boolean checkAmount(CapitalAssetInformation capitalAsset, String errorPathPrefix) {
boolean amountMatch = true;
KualiDecimal distributedAccountLinesAmount = new KualiDecimal(0);
List<CapitalAssetAccountsGroupDetails> groupAccounts = capitalAsset.getCapitalAssetAccountsGroupDetails();
for (CapitalAssetAccountsGroupDetails groupAccount: groupAccounts) {
distributedAccountLinesAmount = distributedAccountLinesAmount.add(groupAccount.getAmount());
}
if (capitalAsset.getCapitalAssetLineAmount().compareTo(distributedAccountLinesAmount) != 0) {
//amount from capital asset does not match its accounting lines sum...
GlobalVariables.getMessageMap().putErrorWithoutFullErrorPath(errorPathPrefix, KFSKeyConstants.ERROR_ASSET_LINE_AMOUNT_NOT_EQUAL_TO_DISTRIBUTED_ACCOUNTING_LINES);
return false;
}
return amountMatch;
}
/**
* Sets the accountingDocumentForValidation attribute value.
*
* @param accountingDocumentForValidation The accountingDocumentForValidation to set.
*/
public void setAccountingDocumentForValidation(AccountingDocument accountingDocumentForValidation) {
this.accountingDocumentForValidation = accountingDocumentForValidation;
}
}