/*
 * The Kuali Financial System, a comprehensive financial management system for higher education.
 *
 * Copyright 2005-2016 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.coa.document.validation.impl;

import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.coa.businessobject.AccountDelegateGlobal;
import org.kuali.kfs.coa.businessobject.AccountDelegateGlobalDetail;
import org.kuali.kfs.coa.businessobject.AccountGlobalDetail;
import org.kuali.kfs.coreservice.framework.parameter.ParameterService;
import org.kuali.kfs.kns.document.MaintenanceDocument;
import org.kuali.kfs.krad.bo.PersistableBusinessObject;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.ObjectUtils;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.service.FinancialSystemDocumentTypeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kim.api.identity.Person;

import java.util.List;

/**
 * This class executes specific rules for the {@link DelegateGlobalMaintenanceDocument}
 */
public class DelegateGlobalRule extends GlobalDocumentRuleBase {

    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DelegateGlobalRule.class);

    protected static final KualiDecimal ZERO = KualiDecimal.ZERO;
    protected AccountDelegateGlobal newDelegateGlobal;
    protected static final String DELEGATE_GLOBALS_PREFIX = "delegateGlobals";
    protected static ParameterService parameterService;

    public DelegateGlobalRule() {
        super();
    }

    /**
     * This method sets the convenience objects like newAccount and oldAccount, so you have short and easy handles to the new and
     * old objects contained in the maintenance document. It also calls the BusinessObjectBase.refresh(), which will attempt to load
     * all sub-objects from the DB by their primary keys, if available.
     */
    @Override
    public void setupConvenienceObjects() {

        // setup newDelegateGlobal convenience objects,
        // make sure all possible sub-objects are populated
        newDelegateGlobal = (AccountDelegateGlobal) super.getNewBo();

        // forces refreshes on all the sub-objects in the lists
        for (AccountDelegateGlobalDetail delegateGlobal : newDelegateGlobal.getDelegateGlobals()) {
            delegateGlobal.refreshNonUpdateableReferences();
        }
        for (AccountGlobalDetail accountGlobalDetail : newDelegateGlobal.getAccountGlobalDetails()) {
            accountGlobalDetail.refreshNonUpdateableReferences();
        }
    }

    /**
     * This checks some basic rules for document approval Specifically it calls the following:
     * <ul>
     * <li>{@link DelegateGlobalRule#checkSimpleRulesAllLines()}</li>
     * <li>{@link DelegateGlobalRule#checkOnlyOneChartErrorWrapper(List)}</li>
     * <li>{@link DelegateGlobalRule#checkForPrimaryDelegateAllLines()}</li>
     * </ul>
     * fails if any rules fail
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomApproveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomApproveDocumentBusinessRules(MaintenanceDocument document) {
        boolean success = true;
        setupConvenienceObjects();
        // check simple rules
        success &= checkSimpleRulesAllLines(document);

        success &= checkOnlyOneChartErrorWrapper(newDelegateGlobal.getAccountGlobalDetails());

        // check for primary routing
        success &= checkForPrimaryDelegateAllLines();
        return success;
    }

    /**
     * This checks some basic rules for document routing Specifically it calls the following:
     * <ul>
     * <li>{@link DelegateGlobalRule#checkSimpleRulesAllLines()}</li>
     * <li>{@link DelegateGlobalRule#checkAccountDetails(List)}</li>
     * <li>{@link DelegateGlobalRule#checkForPrimaryDelegateAllLines()}</li>
     * </ul>
     * fails if any rules fail
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomRouteDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomRouteDocumentBusinessRules(MaintenanceDocument document) {
        boolean success = true;
        setupConvenienceObjects();
        // check simple rules
        success &= checkSimpleRulesAllLines(document);

        success &= checkAccountDetails(newDelegateGlobal.getAccountGlobalDetails());

        // check for primary routing
        success &= checkForPrimaryDelegateAllLines();
        return success;
    }

    /**
     * This checks some basic rules for document saving Specifically it calls the following:
     * <ul>
     * <li>{@link DelegateGlobalRule#checkSimpleRulesAllLines()}</li>
     * <li>{@link DelegateGlobalRule#checkOnlyOneChartErrorWrapper(List)}</li>
     * <li>{@link DelegateGlobalRule#checkForPrimaryDelegateAllLines()}</li>
     * </ul>
     * fails if any rules fail
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomSaveDocumentBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument)
     */
    @Override
    protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
        setupConvenienceObjects();
        // check simple rules
        checkSimpleRulesAllLines(document);

        checkOnlyOneChartErrorWrapper(newDelegateGlobal.getAccountGlobalDetails());

        // check for primary routing
        checkForPrimaryDelegateAllLines();
        return true;
    }

    /**
     * This checks to see if there are any accounts in the details collection if there are then it calls
     * {@link DelegateGlobalRule#checkAccountDetails(AccountGlobalDetail)}
     *
     * @param details - collection of {@link AccountGlobalDetail}s
     * @return false if there are no objects in the collection or any one of the {@link AccountGlobalDetail} fail
     */
    public boolean checkAccountDetails(List<AccountGlobalDetail> details) {
        boolean success = true;

        // check if there are any accounts
        if (details.size() == 0) {
            putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + "accountGlobalDetails.accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_NO_ACCOUNTS);
            success = false;
        } else {
            // check each account
            int index = 0;
            for (AccountGlobalDetail dtl : details) {
                String errorPath = MAINTAINABLE_ERROR_PREFIX + "accountGlobalDetails[" + index + "]";
                GlobalVariables.getMessageMap().addToErrorPath(errorPath);
                success &= checkAccountDetails(dtl);
                GlobalVariables.getMessageMap().removeFromErrorPath(errorPath);
                index++;
            }
            success &= checkOnlyOneChartErrorWrapper(details);
        }

        return success;
    }

    /**
     * This checks to make sure that each {@link AccountGlobalDetail} has a valid {@link Account}
     *
     * @param dtl - the {@link AccountGlobalDetail}
     * @return false if it does not have a valid {@link Account}
     */
    public boolean checkAccountDetails(AccountGlobalDetail dtl) {
        boolean success = true;
        int originalErrorCount = GlobalVariables.getMessageMap().getErrorCount();
        getDictionaryValidationService().validateBusinessObject(dtl);
        if (StringUtils.isNotBlank(dtl.getAccountNumber()) && StringUtils.isNotBlank(dtl.getChartOfAccountsCode())) {
            dtl.refreshReferenceObject("account");
            if (ObjectUtils.isNull(dtl.getAccount())) {
                GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_ACCOUNT_INVALID_ACCOUNT, new String[]{dtl.getChartOfAccountsCode(), dtl.getAccountNumber()});
            }
        }
        success &= GlobalVariables.getMessageMap().getErrorCount() == originalErrorCount;

        return success;
    }

    /**
     * This method checks the simple rules for all lines at once and gets called on save, submit, etc. but not on add Specifically
     * it calls the following:
     * <ul>
     * <li>{@link DelegateGlobalRule#checkDelegateUserRules(DelegateGlobalDetail, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkDelegateForNullToAmount(KualiDecimal, KualiDecimal, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkDelegateToAmtGreaterThanFromAmt(KualiDecimal, KualiDecimal, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkDelegateFromAmtGreaterThanEqualZero(KualiDecimal, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkPrimaryRouteRules(List, DelegateGlobalDetail, Integer, boolean)}</li>
     * </ul>
     *
     * @return
     */
    protected boolean checkSimpleRulesAllLines(MaintenanceDocument document) {

        boolean success = true;
        // check if there are any accounts
        if (newDelegateGlobal.getDelegateGlobals().size() == 0) {
            putFieldError(KFSConstants.MAINTENANCE_ADD_PREFIX + KFSPropertyConstants.DELEGATE_GLOBALS + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_CHANGE_NO_DELEGATE);
            success = false;
        } else {
            // check each delegate
            int i = 0;
            final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
            for (AccountDelegateGlobalDetail newDelegateGlobalDetail : newDelegateGlobal.getDelegateGlobals()) {
                KualiDecimal fromAmount = newDelegateGlobalDetail.getApprovalFromThisAmount();
                KualiDecimal toAmount = newDelegateGlobalDetail.getApprovalToThisAmount();

                success &= checkDelegateUserRules(document, newDelegateGlobalDetail, i, false);

                // FROM amount must be >= 0 (may not be negative)
                success &= checkDelegateFromAmtGreaterThanEqualZero(fromAmount, i, false);

                // TO amount must be >= FROM amount or Zero
                success &= checkDelegateToAmtGreaterThanFromAmt(fromAmount, toAmount, i, false);

                success &= checkDelegateDocumentTypeCode(newDelegateGlobalDetail.getFinancialDocumentTypeCode(), documentService);

                // increment counter for delegate changes list
                i++;
            }
            //KFSCNTRB-1730- check total number doesn't exceed parameter
            String maxAccountDelegatesString = getParameterService().getParameterValueAsString(AccountDelegateGlobal.class, KFSConstants.ChartApcParms.MAXIMUM_ACCOUNT_DELEGATES);
            if (maxAccountDelegatesString != null && !maxAccountDelegatesString.isEmpty()) {
                int maxAccountDelegates = Integer.parseInt(maxAccountDelegatesString);
                int sumAccountDelegates = newDelegateGlobal.getDelegateGlobals().size() * newDelegateGlobal.getAccountGlobalDetails().size();
                if (sumAccountDelegates > maxAccountDelegates) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, KFSKeyConstants.ERROR_DOCUMENT_DELEGATE_MAXIMUM_ACCOUNT_DELEGATES, maxAccountDelegatesString, Integer.toString(sumAccountDelegates));
                    success = false;
                }
            }
        }
        return success;
    }


    /**
     * This method will check through each delegate referenced in the DelegateGlobal to ensure that there is one and only primary
     * for each account and doctype
     *
     * @return false if there is more than one primary delegate
     */
    protected boolean checkForPrimaryDelegateAllLines() {
        boolean success = true;
        int i = 0;
        for (AccountDelegateGlobalDetail newDelegateGlobalDetail : newDelegateGlobal.getDelegateGlobals()) {
            success &= checkPrimaryRouteRules(newDelegateGlobal.getDelegateGlobals(), newDelegateGlobalDetail, new Integer(i), false);
            i++;
        }
        return success;
    }

    /**
     * This method checks to see if the from amount is greater than zero
     *
     * @param fromAmount
     * @param lineNum
     * @return false if from amount less than zero
     */
    protected boolean checkDelegateFromAmtGreaterThanEqualZero(KualiDecimal fromAmount, int lineNum, boolean add) {
        boolean success = true;
        if (ObjectUtils.isNotNull(fromAmount)) {
            if (fromAmount.isLessThan(ZERO)) {
                String errorPath = KFSConstants.EMPTY_STRING;
                if (add) {
                    errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + "approvalFromThisAmount";
                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE);
                } else {
                    errorPath = DELEGATE_GLOBALS_PREFIX + "[" + lineNum + "]." + "approvalFromThisAmount";
                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_FROM_AMOUNT_NONNEGATIVE);
                }
                success &= false;
            }
        }
        return success;
    }

    /**
     * This method checks to see if the to Amount is greater than the from amount
     *
     * @param fromAmount
     * @param toAmount
     * @param lineNum
     * @return false if to amount less than from amount
     */
    protected boolean checkDelegateToAmtGreaterThanFromAmt(KualiDecimal fromAmount, KualiDecimal toAmount, int lineNum, boolean add) {
        boolean success = true;
        if (ObjectUtils.isNotNull(toAmount)) {

            if (!ObjectUtils.isNull(fromAmount)) {
                // case if FROM amount is non-null and positive, disallow TO amount being less if it is not ZERO (another indicator
                // of infinity)
                if (!toAmount.equals(ZERO) && toAmount.isLessThan(fromAmount)) {
                    String errorPath = KFSConstants.EMPTY_STRING;
                    if (add) {
                        errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + "approvalToThisAmount";
                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
                    } else {
                        errorPath = DELEGATE_GLOBALS_PREFIX + "[" + lineNum + "]." + "approvalToThisAmount";
                        putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
                    }
                    success &= false;
                }
            }

            if (toAmount.isLessThan(KualiDecimal.ZERO)) {
                String errorPath = KFSConstants.EMPTY_STRING;
                if (add) {
                    errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + "approvalToThisAmount";
                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
                } else {
                    errorPath = DELEGATE_GLOBALS_PREFIX + "[" + lineNum + "]." + "approvalToThisAmount";
                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_TO_AMOUNT_MORE_THAN_FROM_OR_ZERO);
                }
                success &= false;
            }
        }
        return success;
    }

    /**
     * Validates the document type code for the delegate, to make sure it is a Financial System document type code
     *
     * @param documentTypeCode the document type code to check
     * @param delegateService  a helpful instance of the delegate service, so new ones don't have to be created all the time
     * @return true if the document type code is valid, false otherwise
     */
    protected boolean checkDelegateDocumentTypeCode(String documentTypeCode, FinancialSystemDocumentTypeService documentService) {
        if (!documentService.isFinancialSystemDocumentType(documentTypeCode)) {
            String errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE;
            putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_INVALID_DOC_TYPE, new String[]{documentTypeCode, KFSConstants.ROOT_DOCUMENT_TYPE});

            return false;
        }
        return true;
    }

    /**
     * This method validates the rule that says there can be only one PrimaryRoute delegate for each given docType. It checks the
     * delegateGlobalToTest against the list, to determine whether adding this new delegateGlobalToTest would violate any
     * PrimaryRoute business rule violations. If any of the incoming variables is null or empty, the method will do nothing, and
     * return Null. It will only process the business rules if there is sufficient data to do so.
     *
     * @param delegateGlobalToTest A delegateGlobal line that you want to test against the list.
     * @param delegateGlobals      A List of delegateGlobal items that is being tested against.
     * @return Null if the business rule passes, or an Integer value greater than zero, representing the line that the new line is
     * conflicting with
     */
    protected Integer checkPrimaryRoutePerDocType(AccountDelegateGlobalDetail delegateGlobalToTest, List<AccountDelegateGlobalDetail> delegateGlobals, Integer testLineNum) {

        // exit immediately if the adding line isnt a Primary routing
        if (delegateGlobalToTest == null || delegateGlobals == null || delegateGlobals.isEmpty()) {
            return null;
        }
        if (!delegateGlobalToTest.getAccountDelegatePrimaryRoutingIndicator()) {
            return null;
        }
        if (StringUtils.isBlank(delegateGlobalToTest.getFinancialDocumentTypeCode())) {
            return null;
        }

        // at this point, the delegateGlobal being added is a Primary for ALL docTypes, so we need to
        // test whether any in the existing list are also Primary, regardless of docType
        String docType = delegateGlobalToTest.getFinancialDocumentTypeCode();
        AccountDelegateGlobalDetail delegateGlobal = null;
        for (int lineNumber = 0; lineNumber < delegateGlobals.size(); lineNumber++) {
            delegateGlobal = delegateGlobals.get(lineNumber);
            if (delegateGlobal.getAccountDelegatePrimaryRoutingIndicator()) {
                if (docType.equalsIgnoreCase(delegateGlobal.getFinancialDocumentTypeCode())) {
                    if (testLineNum == null) {
                        return new Integer(lineNumber);
                    } else if (!(testLineNum.intValue() == lineNumber)) {
                        return new Integer(lineNumber);
                    }
                }
            }
        }

        return null;
    }

    /**
     * This checks that the primary routing for delegates is correct, specifically that - there is not already a primary route
     * delegate setup for this {@link Account}
     *
     * @param delegateGlobals
     * @param delegateGlobalToTest
     * @param lineNum
     * @param add
     * @return
     */
    protected boolean checkPrimaryRouteRules(List<AccountDelegateGlobalDetail> delegateGlobals, AccountDelegateGlobalDetail delegateGlobalToTest, Integer lineNum, boolean add) {
        boolean success = true;

        String errorPath = "";
        Integer result = null;
        for (AccountDelegateGlobalDetail delegateGlobal : delegateGlobals) {

            result = checkPrimaryRoutePerDocType(delegateGlobalToTest, delegateGlobals, lineNum);
            if (result != null) {
                if (add) {
                    errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + "financialDocumentTypeCode";
                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_DELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE);
                } else {
                    errorPath = DELEGATE_GLOBALS_PREFIX + "[" + lineNum.toString() + "]." + "financialDocumentTypeCode";
                    putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_GLOBAL_DELEGATEMAINT_PRIMARY_ROUTE_ALREADY_EXISTS_FOR_DOCTYPE);
                }
                success &= false;
            }
        }
        return success;
    }

    /**
     * This checks that the delegate for this {@link Account} exists and is valid (active and a professional)
     *
     * @param delegateGlobal
     * @param lineNum
     * @param add
     * @return false if the delegate for the {@link Account} doesn't exist or isn't valid
     */
    protected boolean checkDelegateUserRules(MaintenanceDocument document, AccountDelegateGlobalDetail delegateGlobal, int lineNum, boolean add) {
        boolean success = true;
        String errorPath = KFSConstants.EMPTY_STRING;

        Person accountDelegate = delegateGlobal.getAccountDelegate();
        if (StringUtils.isBlank(delegateGlobal.getAccountDelegateUniversalId()) || ObjectUtils.isNull(accountDelegate)) {
            if (add) {
                errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + "accountDelegate.principalName";
                putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_DOESNT_EXIST);
            } else {
                errorPath = DELEGATE_GLOBALS_PREFIX + "[" + lineNum + "]." + "accountDelegate.principalName";
                putFieldError(errorPath, KFSKeyConstants.ERROR_DOCUMENT_ACCTDELEGATEMAINT_USER_DOESNT_EXIST);
            }

            success &= false;
        } else if (!getDocumentHelperService().getDocumentAuthorizer(document).isAuthorized(document, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name, accountDelegate.getPrincipalId())) {
            if (add) {
                errorPath = KFSConstants.MAINTENANCE_ADD_PREFIX + DELEGATE_GLOBALS_PREFIX + "." + "accountDelegate.principalName";
                putFieldError(errorPath, KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[]{accountDelegate.getName(), KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name});
            } else {
                errorPath = DELEGATE_GLOBALS_PREFIX + "[" + lineNum + "]." + "accountDelegate.principalName";
                putFieldError(errorPath, KFSKeyConstants.ERROR_USER_MISSING_PERMISSION, new String[]{accountDelegate.getName(), KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.namespace, KFSConstants.PermissionNames.SERVE_AS_FISCAL_OFFICER_DELEGATE.name});
            }

            success &= false;
        }

        return success;
    }

    /**
     * This checks that when a new line is added (either {@link AccountGlobalDetail} or {@link DelegateGlobalDetail}) that the
     * appropriate rules are run on the new lines being added on {@link AccountGlobalDetail}: - make sure that the account number
     * and chart are entered
     * <ul>
     * <li>{@link DelegateGlobalRule#checkAccountDetails(AccountGlobalDetail)}</li>
     * </ul>
     * on {@link DelegateGlobalDetail}
     * <ul>
     * <li>{@link DelegateGlobalRule#checkDelegateFromAmtGreaterThanEqualZero(KualiDecimal, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkDelegateForNullToAmount(KualiDecimal, KualiDecimal, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkDelegateToAmtGreaterThanFromAmt(KualiDecimal, KualiDecimal, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkDelegateUserRules(DelegateGlobalDetail, int, boolean)}</li>
     * <li>{@link DelegateGlobalRule#checkPrimaryRouteRules(List, DelegateGlobalDetail, Integer, boolean)}</li>
     * </ul>
     *
     * @see org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase#processCustomAddCollectionLineBusinessRules(org.kuali.rice.kns.document.MaintenanceDocument,
     * java.lang.String, org.kuali.rice.krad.bo.PersistableBusinessObject)
     */
    @Override
    public boolean processCustomAddCollectionLineBusinessRules(MaintenanceDocument document, String collectionName, PersistableBusinessObject bo) {
        boolean success = true;
        if (bo instanceof AccountGlobalDetail) {
            AccountGlobalDetail detail = (AccountGlobalDetail) bo;
            // make sure that both primary keys are available for this object
            if (!checkEmptyValue(detail.getAccountNumber())) {
                // put an error about accountnumber
                GlobalVariables.getMessageMap().putError("accountNumber", KFSKeyConstants.ERROR_REQUIRED, "Account Number");
                success &= false;
            }
            if (!checkEmptyValue(detail.getChartOfAccountsCode())) {
                // put an error about chart code
                GlobalVariables.getMessageMap().putError("chartOfAccountsCode", KFSKeyConstants.ERROR_REQUIRED, "Chart of Accounts Code");
                success &= false;
            }
            success &= checkAccountDetails(detail);
        } else if (bo instanceof AccountDelegateGlobalDetail) {
            AccountDelegateGlobalDetail detail = (AccountDelegateGlobalDetail) bo;
            detail.refreshNonUpdateableReferences();
            KualiDecimal fromAmount = detail.getApprovalFromThisAmount();
            KualiDecimal toAmount = detail.getApprovalToThisAmount();

            // FROM amount must be >= 0 (may not be negative)
            success &= checkDelegateFromAmtGreaterThanEqualZero(fromAmount, 0, true);

            // TO amount must be >= FROM amount or Zero
            success &= checkDelegateToAmtGreaterThanFromAmt(fromAmount, toAmount, 0, true);

            // check the user that is being added
            // TODO: add back in once the user issues have been fixed
            success &= checkDelegateUserRules(document, detail, 0, true);

            // check the routing
            success &= checkPrimaryRouteRules(newDelegateGlobal.getDelegateGlobals(), detail, null, true);

            final FinancialSystemDocumentTypeService documentService = SpringContext.getBean(FinancialSystemDocumentTypeService.class);
            success &= checkDelegateDocumentTypeCode(detail.getFinancialDocumentTypeCode(), documentService);

        }
        return success;
    }


    public ParameterService getParameterService() {
        if (parameterService == null) {
            parameterService = SpringContext.getBean(ParameterService.class);
        }
        return parameterService;
    }
}
