/*
 * 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.fp.document;

import org.kuali.kfs.fp.businessobject.Check;
import org.kuali.kfs.fp.businessobject.CheckBase;
import org.kuali.kfs.fp.businessobject.CoinDetail;
import org.kuali.kfs.fp.businessobject.CurrencyDetail;
import org.kuali.kfs.fp.businessobject.DepositCashReceiptControl;
import org.kuali.kfs.fp.document.service.CashReceiptService;
import org.kuali.kfs.fp.document.validation.event.AddCheckEvent;
import org.kuali.kfs.fp.document.validation.event.DeleteCheckEvent;
import org.kuali.kfs.fp.document.validation.event.UpdateCheckEvent;
import org.kuali.kfs.fp.service.CheckService;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.krad.document.Copyable;
import org.kuali.kfs.krad.rules.rule.event.KualiDocumentEvent;
import org.kuali.kfs.krad.rules.rule.event.SaveDocumentEvent;
import org.kuali.kfs.krad.service.BusinessObjectService;
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.KFSConstants.CurrencyCoinSources;
import org.kuali.kfs.sys.KFSConstants.DocumentStatusCodes;
import org.kuali.kfs.sys.KFSConstants.DocumentStatusCodes.CashReceipt;
import org.kuali.kfs.sys.businessobject.ChartOrgHolder;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
import org.kuali.kfs.sys.businessobject.SufficientFundsItem;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AmountTotaling;
import org.kuali.kfs.sys.document.service.DebitDeterminerService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.core.web.format.CurrencyFormatter;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kew.api.document.DocumentStatus;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
import org.kuali.rice.kim.api.identity.Person;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * This is the business object that represents the CashReceiptDocument in Kuali. This is a transactional document that will
 * eventually post transactions to the G/L. It integrates with workflow. Since a Cash Receipt is a one sided transactional document,
 * only accepting funds into the university, the accounting line data will be held in the source accounting line data structure
 * only.
 */
public class CashReceiptDocument extends CashReceiptFamilyBase implements Copyable, AmountTotaling, CapitalAssetEditable {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(CashReceiptDocument.class);

    public static final String CHECK_ENTRY_DETAIL = "individual";
    public static final String CHECK_ENTRY_TOTAL = "totals";

    public static final String DOCUMENT_TYPE = "CR";
    public static final String REQUIRE_REVIEW_SPLIT = "RequireChangeRequestReview";

    // child object containers - for all the different reconciliation detail sections
    protected String checkEntryMode = CHECK_ENTRY_DETAIL;
    protected List<Check> checks = new ArrayList<Check>();
    protected List<Check> confirmedChecks = new ArrayList<Check>();

    // deposit controls
    protected List<DepositCashReceiptControl> depositCashReceiptControl = new ArrayList<DepositCashReceiptControl>();

    // incrementers for detail lines
    protected Integer nextCheckSequenceId = new Integer(1);
    protected Integer nextConfirmedCheckSequenceId = new Integer(1);

    // monetary attributes

    // These total amount fields except the check ones aren't needed, since their values are computed by the getters;
    // the setters are never used and aren't needed; also these fields exist in DB, but the values are never set.
    protected KualiDecimal totalCurrencyAmount = KualiDecimal.ZERO;
    protected KualiDecimal totalCheckAmount = KualiDecimal.ZERO;
    protected KualiDecimal totalCoinAmount = KualiDecimal.ZERO;
    protected KualiDecimal sumTotalAmount = KualiDecimal.ZERO;
    protected KualiDecimal totalConfirmedCurrencyAmount = KualiDecimal.ZERO;
    protected KualiDecimal totalConfirmedCheckAmount = KualiDecimal.ZERO;
    protected KualiDecimal totalConfirmedCoinAmount = KualiDecimal.ZERO;
    protected KualiDecimal totalChangeAmount = KualiDecimal.ZERO;

    protected CurrencyDetail currencyDetail;
    protected CoinDetail coinDetail;

    protected CurrencyDetail confirmedCurrencyDetail;
    protected CoinDetail confirmedCoinDetail;

    protected CurrencyDetail changeCurrencyDetail;
    protected CoinDetail changeCoinDetail;

    protected CurrencyDetail confirmedChangeCurrencyDetail;
    protected CoinDetail confirmedChangeCoinDetail;

    protected boolean recategorized;

    protected String createDate;

    /**
     * Initializes the array lists and line incrementers.
     */
    public CashReceiptDocument() {
        super();

        initializeCampusLocationCode();
        initializeCashChangeDetails();
    }

    /**
     * Initializes all currency/coin and change currency/coin details.
     * This ensures that these details won't be null.
     */
    protected void initializeCashChangeDetails() {
        currencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_RECEIPTS);
        coinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_RECEIPTS);

        confirmedCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);
        confirmedCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);

        changeCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_REQUEST);
        changeCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_REQUEST);

        confirmedChangeCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
        confirmedChangeCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
    }

    /**
     * Sets the non-amount primary key fields for all currency/coin and change currency/coin details.
     * This ensures that the key fields of these details are populated properly, in case they weren't during initialization.
     */
    protected void setCashChangeDetailKeys() {
        currencyDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_RECEIPTS);
        coinDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_RECEIPTS);

        confirmedCurrencyDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);
        confirmedCoinDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);

        changeCurrencyDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_REQUEST);
        changeCoinDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_REQUEST);

        confirmedChangeCurrencyDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
        confirmedChangeCoinDetail.setKeys(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
    }

    /**
     * Gets the totalCurrencyAmount attribute.
     *
     * @return Returns the totalCurrencyAmount.
     */
    public KualiDecimal getTotalCurrencyAmount() {
        return (currencyDetail != null) ? currencyDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the totalConfirmedCurrencyAmount attribute.
     *
     * @return Returns the totalConfirmedCurrencyAmount.
     */
    public KualiDecimal getTotalConfirmedCurrencyAmount() {
        return (confirmedCurrencyDetail != null) ? confirmedCurrencyDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the totalChangeCurrencyAmount attribute.
     *
     * @return Returns the totalChangeCurrencyAmount.
     */
    public KualiDecimal getTotalChangeCurrencyAmount() {
        return (changeCurrencyDetail != null) ? changeCurrencyDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the total amount of depositable checks
     *
     * @return
     */
    public KualiDecimal getTotalDepositableCheckAmount() {
        KualiDecimal totalDepositableCheckAmount = KualiDecimal.ZERO;
        List<Check> checks = getConfirmedChecks();
        if (checks != null && !checks.isEmpty()) {
            for (Check check : checks) {
                if (check.getCashieringStatus().equals("C")) {
                    totalDepositableCheckAmount = totalDepositableCheckAmount.add(check.getAmount());
                }
            }
        }
        return totalDepositableCheckAmount;
    }

    /**
     * Checks if there are checks to be deposited
     *
     * @return
     */
    public boolean existDepositableChecks() {
        return getTotalDepositableCheckAmount().isGreaterThan(KualiDecimal.ZERO) ? true : false;
    }

    /**
     * This method returns the total depositable check amount as a currency formatted string.
     *
     * @return
     */
    public String getCurrencyFormattedTotalDepositableCheckAmount() {
        return (String) new CurrencyFormatter().format(getTotalDepositableCheckAmount());
    }

    /**
     * This method returns the cash total amount as a currency formatted string.
     *
     * @return String
     */
    public String getCurrencyFormattedTotalCurrencyAmount() {
        return (String) new CurrencyFormatter().format(getTotalCurrencyAmount());
    }


    public String getCurrencyFormattedTotalConfirmedCurrencyAmount() {
        return (String) new CurrencyFormatter().format(getTotalConfirmedCurrencyAmount());
    }

    /**
     * Sets the totalCurrencyAmount attribute value.
     *
     * @param totalCurrencyAmount The totalCurrencyAmount to set.
     */
    public void setTotalCurrencyAmount(KualiDecimal totalCurrencyAmount) {
        this.totalCurrencyAmount = totalCurrencyAmount;
    }

    /**
     * Sets the totalConfirmedCurrencyAmount attribute value.
     *
     * @param totalConfirmedCurrencyAmount The totalConfirmedCurrencyAmount to set.
     */
    public void setTotalConfirmedCurrencyAmount(KualiDecimal totalConfirmedCurrencyAmount) {
        this.totalConfirmedCurrencyAmount = totalConfirmedCurrencyAmount;
    }


    /**
     * @param checkEntryMode
     */
    public void setCheckEntryMode(String checkEntryMode) {
        this.checkEntryMode = checkEntryMode;
    }

    /**
     * @return checkEntryMode
     */
    public String getCheckEntryMode() {
        return checkEntryMode;
    }


    /**
     * Gets the checks attribute.
     *
     * @return Returns the checks.
     */
    public List<Check> getChecks() {
        return checks;
    }

    /**
     * Sets the checks attribute value.
     *
     * @param checks The checks to set.
     */
    public void setChecks(List<Check> checks) {
        this.checks = checks;
    }

    /**
     * Gets the confirmed checks attribute.
     *
     * @return Returns the confirmed checks.
     */
    public List<Check> getConfirmedChecks() {
        return confirmedChecks;
    }

    /**
     * Sets the confirmed checks attribute value.
     *
     * @param confirmedChecks The checks to set.
     */
    public void setConfirmedChecks(List<Check> confirmedChecks) {
        this.confirmedChecks = confirmedChecks;
    }

    /**
     * Gets the number of checks, since Sun doesn't have a direct getter for collection size
     *
     * @return the number of checks
     */
    public int getCheckCount() {
        int count = 0;
        if (ObjectUtils.isNotNull(checks)) {
            count = checks.size();
        }
        return count;
    }

    /**
     * Gets the number of confirmed checks
     *
     * @return the number of checks
     */
    public int getConfirmedCheckCount() {
        int count = 0;
        if (ObjectUtils.isNotNull(confirmedChecks)) {
            count = confirmedChecks.size();
        }
        return count;
    }


    /**
     * Adds a new check to the list.
     *
     * @param check
     */
    public void addCheck(Check check) {
        check.setSequenceId(this.nextCheckSequenceId);

        this.checks.add(check);

        this.nextCheckSequenceId = new Integer(this.nextCheckSequenceId.intValue() + 1);

        setTotalCheckAmount(getTotalCheckAmount().add(check.getAmount()));
    }

    /**
     * Adds a new confirmed check to the list.
     *
     * @param check
     */
    public void addConfirmedCheck(Check check) {
        check.setSequenceId(this.nextConfirmedCheckSequenceId);

        this.confirmedChecks.add(check);

        this.nextConfirmedCheckSequenceId = new Integer(this.nextConfirmedCheckSequenceId.intValue() + 1);

        setTotalConfirmedCheckAmount(getTotalConfirmedCheckAmount().add(check.getAmount()));
    }

    /**
     * Retrieve a particular check at a given index in the list of checks.
     *
     * @param index
     * @return Check
     */
    public Check getCheck(int index) {
        while (this.checks.size() <= index) {
            checks.add(createNewCheck());
        }
        return checks.get(index);
    }

    /**
     * Retrieve a particular check at a given index in the list of confirmed checks.
     *
     * @param index
     * @return Check
     */
    public Check getConfirmedCheck(int index) {
        while (this.confirmedChecks.size() <= index) {
            confirmedChecks.add(createNewConfirmedCheck());
        }
        return confirmedChecks.get(index);
    }


    /**
     * @see org.kuali.kfs.sys.document.AccountingDocumentBase#checkSufficientFunds()
     */
    @Override
    public List<SufficientFundsItem> checkSufficientFunds() {
        LOG.debug("checkSufficientFunds() started");

        // This document does not do sufficient funds checking
        return new ArrayList<SufficientFundsItem>();
    }


    /**
     * This method removes a check from the list and updates the total appropriately.
     *
     * @param index
     */
    public void removeCheck(int index) {
        Check check = checks.remove(index);
        KualiDecimal newTotalCheckAmount = getTotalCheckAmount().subtract(check.getAmount());
        // if the totalCheckAmount goes negative, bring back to zero.
        if (newTotalCheckAmount.isNegative()) {
            newTotalCheckAmount = KualiDecimal.ZERO;
        }
        setTotalCheckAmount(newTotalCheckAmount);
    }

    /**
     * This method removes a confirmed check from the list and updates the total appropriately.
     *
     * @param index
     */
    public void removeConfirmedCheck(int index) {
        Check check = confirmedChecks.remove(index);
        KualiDecimal newTotalCheckAmount = getTotalConfirmedCheckAmount().subtract(check.getAmount());
        // if the totalCheckAmount goes negative, bring back to zero.
        if (newTotalCheckAmount.isNegative()) {
            newTotalCheckAmount = KualiDecimal.ZERO;
        }
        setTotalConfirmedCheckAmount(newTotalCheckAmount);
    }

    /**
     * Gets the nextCheckSequenceId attribute.
     *
     * @return Returns the nextCheckSequenceId.
     */
    public Integer getNextCheckSequenceId() {
        return nextCheckSequenceId;
    }

    /**
     * Sets the nextCheckSequenceId attribute value.
     *
     * @param nextCheckSequenceId The nextCheckSequenceId to set.
     */
    public void setNextCheckSequenceId(Integer nextCheckSequenceId) {
        this.nextCheckSequenceId = nextCheckSequenceId;
    }

    /**
     * Gets the nextConfirmedCheckSequenceId attribute.
     *
     * @return Returns the nextConfirmedCheckSequenceId.
     */
    public Integer getNextConfirmedCheckSequenceId() {
        return nextConfirmedCheckSequenceId;
    }

    /**
     * Sets the nextCheckSequenceId attribute value.
     *
     * @param nextCheckSequenceId The nextCheckSequenceId to set.
     */
    public void setNextConfirmedCheckSequenceId(Integer nextConfirmedCheckSequenceId) {
        this.nextConfirmedCheckSequenceId = nextConfirmedCheckSequenceId;
    }

    /**
     * Gets the totalCheckAmount attribute.
     *
     * @return Returns the totalCheckAmount.
     */
    public KualiDecimal getTotalCheckAmount() {
        if (totalCheckAmount == null) {
            setTotalCheckAmount(KualiDecimal.ZERO);
        }
        return totalCheckAmount;
    }

    /**
     * Gets the totalConfirmedCheckAmount attribute.
     *
     * @return Returns the totalCheckAmount.
     */
    public KualiDecimal getTotalConfirmedCheckAmount() {
        if (totalConfirmedCheckAmount == null) {
            setTotalConfirmedCheckAmount(KualiDecimal.ZERO);
        }
        return totalConfirmedCheckAmount;
    }

    /**
     * Gets the totalConfirmedCheckAmount attribute.
     *
     * @return Returns the totalCheckAmount.
     */
    public KualiDecimal getTotalConfirmedCheckAmountForInterim() {
        if (totalConfirmedCheckAmount == null || getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode().equals(CashReceipt.INTERIM)) {
            setTotalConfirmedCheckAmount(KualiDecimal.ZERO);
        }
        return totalConfirmedCheckAmount;
    }

    /**
     * This method returns the check total amount as a currency formatted string.
     *
     * @return String
     */
    public String getCurrencyFormattedTotalCheckAmount() {
        return (String) new CurrencyFormatter().format(getTotalCheckAmount());
    }

    /**
     * This method returns the confirmed check total amount as a currency formatted string.
     *
     * @return String
     */
    public String getCurrencyFormattedTotalConfirmedCheckAmount() {
        String amountString = (String) new CurrencyFormatter().format(getTotalConfirmedCheckAmount());
        return amountString;

    }

    /**
     * Sets the totalCheckAmount attribute value.
     *
     * @param totalCheckAmount The totalCheckAmount to set.
     */
    public void setTotalCheckAmount(KualiDecimal totalCheckAmount) {
        this.totalCheckAmount = totalCheckAmount;
    }

    /**
     * Sets the totalCheckAmount attribute value.
     *
     * @param totalCheckAmount The totalCheckAmount to set.
     */
    public void setTotalConfirmedCheckAmount(KualiDecimal totalConfirmedCheckAmount) {
        this.totalConfirmedCheckAmount = totalConfirmedCheckAmount;
    }

    /**
     * Gets the totalCoinAmount attribute.
     *
     * @return Returns the totalCoinAmount.
     */
    public KualiDecimal getTotalCoinAmount() {
        return (coinDetail != null) ? coinDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the totalConfirmedCoinAmount attribute.
     *
     * @return Returns the totalConfirmedCoinAmount.
     */
    public KualiDecimal getTotalConfirmedCoinAmount() {
        return (confirmedCoinDetail != null) ? confirmedCoinDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the totalChangeCoinAmount attribute.
     *
     * @return Returns the totalChangeCoinAmount.
     */
    public KualiDecimal getTotalChangeCoinAmount() {
        return (changeCoinDetail != null) ? changeCoinDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * This method returns the coin total amount as a currency formatted string.
     *
     * @return String
     */
    public String getCurrencyFormattedTotalCoinAmount() {
        return (String) new CurrencyFormatter().format(getTotalCoinAmount());
    }

    /**
     * This method returns the confirmed coin total amount as a currency formatted string.
     *
     * @return String
     */
    public String getCurrencyFormattedTotalConfirmedCoinAmount() {
        return (String) new CurrencyFormatter().format(getTotalConfirmedCoinAmount());
    }

    /**
     * Deprecated: Use getTotalConfirmedNetAmount() instead.
     * returns (confirmed currency + confirmed coin - change amount)
     *
     * @return
     */
    @Deprecated
    public KualiDecimal getGrandTotalConfirmedCashAmount() {
        return getTotalConfirmedNetAmount();
    }


    /**
     * returns (confirmed currency + confirmed coin - change amount) as a currency formatted string
     *
     * @return
     */
    public String getCurrencyFormattedGrandTotalConfirmedCashAmount() {
        return (String) new CurrencyFormatter().format(getTotalConfirmedNetAmount());
    }

    /**
     * Sets the totalCoinAmount attribute value.
     *
     * @param totalCoinAmount The totalCoinAmount to set.
     */
    public void setTotalCoinAmount(KualiDecimal totalCoinAmount) {
        this.totalCoinAmount = totalCoinAmount;
    }

    /**
     * Sets the totalConfirmedCoinAmount attribute value.
     *
     * @param totalCoinAmount The totalConfirmedCoinAmount to set.
     */
    public void setTotalConfirmedCoinAmount(KualiDecimal totalConfirmedCoinAmount) {
        this.totalConfirmedCoinAmount = totalConfirmedCoinAmount;
    }

    /**
     * Gets the overall total dollar amount of the document.
     * This is the amount displayed in the document header, and used as one of the document search criteria.
     * It is the net deposit total amount of the CR, and should equal to the accounting line total amount.
     * net deposit = money in - money out;
     * money in = check + currency + coin;
     * money out = change currency + change coin = change total.
     *
     * @return KualiDecimal
     * @see org.kuali.kfs.sys.document.AccountingDocumentBase#getTotalDollarAmount()
     */
    @Override
    public KualiDecimal getTotalDollarAmount() {
        // We should return the net deposit total, which doesn't match the accounting line total when change request is not zero.
        // if CR is preroute, use the original amount;
        if (isPreRoute()) {
            return getTotalNetAmount();
        }
        // otherwise, otherwise use the confirmed amount
        else {
            return getTotalConfirmedNetAmount();
        }
    }

    public KualiDecimal getTotalChangeAmount() {
        return getTotalChangeCoinAmount().add(getTotalChangeCurrencyAmount());
    }

    /**
     * Gets the coinDetail attribute.
     *
     * @return Returns the coinDetail.
     */
    public CoinDetail getCoinDetail() {
        return coinDetail;
    }

    /**
     * Sets the coinDetail attribute value.
     *
     * @param coinDetail The coinDetail to set.
     */
    public void setCoinDetail(CoinDetail coinDetail) {
        this.coinDetail = coinDetail;
    }

    /**
     * Gets the confirmedCoinDetail attribute.
     *
     * @return Returns the confirmedCoinDetail.
     */
    public CoinDetail getConfirmedCoinDetail() {
        return confirmedCoinDetail;
    }

    /**
     * Sets the confirmedCoinDetail attribute value.
     *
     * @param coinDetail The confirmedCoinDetail to set.
     */
    public void setConfirmedCoinDetail(CoinDetail coinDetail) {
        this.confirmedCoinDetail = coinDetail;
    }

    /**
     * Gets the changeCoinDetail attribute.
     *
     * @return Returns the changeCoinDetail.
     */
    public CoinDetail getChangeCoinDetail() {
        return changeCoinDetail;
    }

    /**
     * Sets the changeCoinDetail attribute value.
     *
     * @param coinDetail The changeCoinDetail to set.
     */
    public void setChangeCoinDetail(CoinDetail changeCoinDetail) {
        this.changeCoinDetail = changeCoinDetail;
    }

    /**
     * Gets the currencyDetail attribute.
     *
     * @return Returns the currencyDetail.
     */
    public CurrencyDetail getCurrencyDetail() {
        return currencyDetail;
    }

    /**
     * Sets the currencyDetail attribute value.
     *
     * @param currencyDetail The currencyDetail to set.
     */
    public void setCurrencyDetail(CurrencyDetail currencyDetail) {
        this.currencyDetail = currencyDetail;
    }

    /**
     * Gets the confirmedCurrencyDetail attribute.
     *
     * @return Returns the confirmedCurrencyDetail.
     */
    public CurrencyDetail getConfirmedCurrencyDetail() {
        return confirmedCurrencyDetail;
    }

    /**
     * Sets the confirmedCurrencyDetail attribute value.
     *
     * @param currencyDetail The confirmedCurrencyDetail to set.
     */
    public void setConfirmedCurrencyDetail(CurrencyDetail confirmedCurrencyDetail) {
        this.confirmedCurrencyDetail = confirmedCurrencyDetail;
    }

    /**
     * Gets the changeCurrencyDetail attribute.
     *
     * @return Returns the changeCurrencyDetail.
     */
    public CurrencyDetail getChangeCurrencyDetail() {
        return changeCurrencyDetail;
    }

    /**
     * Sets the changeCurrencyDetail attribute value.
     *
     * @param currencyDetail The changeCurrencyDetail to set.
     */
    public void setChangeCurrencyDetail(CurrencyDetail changeCurrencyDetail) {
        this.changeCurrencyDetail = changeCurrencyDetail;
    }

    /**
     * Retrieves the change total amount in a currency format with commas.
     *
     * @return String
     */
    @Deprecated
    public String getCurrencyFormattedTotalChangeAmount() {
        return (String) new CurrencyFormatter().format(getTotalChangeAmount());
    }

    /**
     * Gets the recategorized attribute.
     *
     * @return Returns the recategorized.
     */
    public boolean isRecategorized() {
        return recategorized;
    }

    /**
     * Sets the recategorized attribute value.
     *
     * @param recategorized The recategorized to set.
     */
    public void setRecategorized(boolean recategorized) {
        this.recategorized = recategorized;
    }

    /**
     * @return sum of the amounts of the current list of checks
     */
    public KualiDecimal calculateCheckTotal() {
        KualiDecimal total = KualiDecimal.ZERO;
        for (Iterator<Check> i = getChecks().iterator(); i.hasNext(); ) {
            Check c = i.next();
            if (null != c.getAmount()) {
                total = total.add(c.getAmount());
            }
        }
        return total;
    }

    /**
     * @return sum of the amounts of the current list of checks
     */
    public KualiDecimal calculateConfirmedCheckTotal() {
        KualiDecimal total = KualiDecimal.ZERO;
        for (Iterator<Check> i = getConfirmedChecks().iterator(); i.hasNext(); ) {
            Check c = i.next();
            if (null != c.getAmount()) {
                total = total.add(c.getAmount());
            }
        }
        return total;
    }


    /**
     * @see org.kuali.rice.kns.document.DocumentBase#prepareForSave()
     */
    @Override
    public void prepareForSave() {
        super.prepareForSave();

        // clear check list if mode is checkTotal
        if (CHECK_ENTRY_TOTAL.equals(getCheckEntryMode())) {
            getChecks().clear();
        }
        // update total if mode is checkDetail
        else {
            setTotalCheckAmount(calculateCheckTotal());
        }
    }

    /**
     * @see org.kuali.rice.kns.document.DocumentBase#processAfterRetrieve()
     */
    @Override
    public void processAfterRetrieve() {
        super.processAfterRetrieve();

        // set to checkTotal mode if no checks
        List<Check> checkList = getChecks();
        if (ObjectUtils.isNull(checkList) || checkList.isEmpty()) {
            setCheckEntryMode(CHECK_ENTRY_TOTAL);
        }
        // set to checkDetail mode if checks (and update the checkTotal, while you're here)
        else {
            setCheckEntryMode(CHECK_ENTRY_DETAIL);
            setTotalCheckAmount(calculateCheckTotal());
        }
        refreshCashDetails();
    }

    /**
     * Override to set the document status to VERIFIED ("V") when the document is FINAL. When the Cash Management document that this
     * is associated with is FINAL approved, this status will be set to APPROVED ("A") to be picked up by the GL for processing.
     * That's done in the doRouteStatusChange() method in the CashManagementDocument.
     *
     * @see org.kuali.rice.kns.document.Document#doRouteStatusChange()
     */
    @Override
    public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
        super.doRouteStatusChange(statusChangeEvent);
        WorkflowDocument workflowDocument = getDocumentHeader().getWorkflowDocument();

        // Workflow Status of PROCESSED --> Kuali Doc Status of Verified
        if (workflowDocument.isProcessed()) {
            this.getFinancialSystemDocumentHeader().setFinancialDocumentStatusCode(CashReceipt.VERIFIED);
            LOG.info("Adding Cash to Cash Drawer");
            SpringContext.getBean(CashReceiptService.class).addCashDetailsToCashDrawer(this);
        }

        this.getCapitalAssetManagementModuleService().deleteDocumentAssetLocks(this);
    }

    /**
     * @see org.kuali.rice.kns.document.DocumentBase#postProcessSave(org.kuali.rice.kns.rule.event.KualiDocumentEvent)
     */
    @Override
    public void postProcessSave(KualiDocumentEvent event) {
        super.postProcessSave(event);

        /*
         * If we only saves a detail when its total is not zero, when the detail was non-zero and saved before, and now it is cleared to 0,
         * it won't be saved, leaving the previously saved non-zero detail still in DB.
         * Next time CR is loaded, the old value will show up again. Basically it won't allow clearing a detail to 0.
         */

        // Set primary keys for all cash/change details, in case the documentNumber was null when it was set during document initialization.
        // The documentNumber would be null if the CR was created from scratch (instead of from copying, or retrieved from DB)
        // and is being saved for the first time at this point.
        setCashChangeDetailKeys();
        BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);

        // if CR is not enroute yet, confirmed details aren't filled yet, so only need to save original details
        if (isPreRoute()) {
            // if currencyDetail not empty, save it
            if (!currencyDetail.isEmpty()) {
                boService.save(currencyDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old currencyDetail
            else if (retrieveCurrencyDetail() != null) {
                boService.delete(currencyDetail);
            }

            // if coinDetail not empty, save it
            if (!coinDetail.isEmpty()) {
                boService.save(coinDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old coinDetail
            else if (retrieveCoinDetail() != null) {
                boService.delete(coinDetail);
            }

            // if changeCurrencyDetail not empty, save it
            if (!changeCurrencyDetail.isEmpty()) {
                boService.save(changeCurrencyDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old changeCurrencyDetail
            else if (retrieveChangeCurrencyDetail() != null) {
                boService.delete(changeCurrencyDetail);
            }

            // if changeCoinDetail not empty, save it
            if (!changeCoinDetail.isEmpty()) {
                boService.save(changeCoinDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old changeCoinDetail
            else if (retrieveChangeCoinDetail() != null) {
                boService.delete(changeCoinDetail);
            }
        }
        // if CR is enroute, original details are not editable, so only need to save the confirmed details
        else {
            // if confirmedCurrencyDetail not empty, save it
            if (!confirmedCurrencyDetail.isEmpty()) {
                boService.save(confirmedCurrencyDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old confirmedCurrencyDetail
            else if (retrieveConfirmedCurrencyDetail() != null) {
                boService.delete(confirmedCurrencyDetail);
            }

            // if confirmedCoinDetail not empty, save it
            if (!confirmedCoinDetail.isEmpty()) {
                boService.save(confirmedCoinDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old confirmedCoinDetail
            else if (retrieveConfirmedCoinDetail() != null) {
                boService.delete(confirmedCoinDetail);
            }

            // if confirmedChangeCurrencyDetail not empty, save it
            if (!confirmedChangeCurrencyDetail.isEmpty()) {
                boService.save(confirmedChangeCurrencyDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old confirmedChangeCurrencyDetail
            else if (retrieveConfirmedChangeCurrencyDetail() != null) {
                boService.delete(confirmedChangeCurrencyDetail);
            }

            // if confirmedChangeCoinDetail not empty, save it
            if (!confirmedChangeCoinDetail.isEmpty()) {
                boService.save(confirmedChangeCoinDetail);
            }
            // otherwise, check if it is previously saved, if yes, delete old confirmedChangeCoinDetail
            else if (retrieveConfirmedChangeCoinDetail() != null) {
                boService.delete(confirmedChangeCoinDetail);
            }
        }

        if (!(event instanceof SaveDocumentEvent)) { // don't lock until they route
            String documentTypeName = SpringContext.getBean(DataDictionaryService.class).getDocumentTypeNameByClass(this.getClass());
            this.getCapitalAssetManagementModuleService().generateCapitalAssetLock(this, documentTypeName);
        }
    }

    /**
     * This method refreshes the currency/coin details for this cash receipt document
     */
    public void refreshCashDetails() {
        CurrencyDetail retrievedCurrencyDetail = retrieveCurrencyDetail();
        CoinDetail retrievedCoinDetail = retrieveCoinDetail();
        CurrencyDetail retrievedConfirmedCurrencyDetail = retrieveConfirmedCurrencyDetail();
        CoinDetail retrievedConfirmedCoinDetail = retrieveConfirmedCoinDetail();
        CurrencyDetail retrievedChangeCurrencyDetail = retrieveChangeCurrencyDetail();
        CoinDetail retrievedChangeCoinDetail = retrieveChangeCoinDetail();
        CurrencyDetail retrievedConfirmedChangeCurrencyDetail = retrieveConfirmedChangeCurrencyDetail();
        CoinDetail retrievedConfirmedChangeCoinDetail = retrieveConfirmedChangeCoinDetail();

        // If this method is called before the cash detail records are inserted into the DB, reset the detail to initial values.
        // In normal cases, when this happens, the details shall be in initial status anyways, since this method should only be called
        // as a post-process after retrieving CR from DB.
        if (retrievedCurrencyDetail != null) {
            currencyDetail = retrievedCurrencyDetail;
        } else {
            currencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_RECEIPTS);
        }
        if (retrievedCoinDetail != null) {
            coinDetail = retrievedCoinDetail;
        } else {
            coinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_RECEIPTS);
        }
        if (retrievedConfirmedCurrencyDetail != null) {
            confirmedCurrencyDetail = retrievedConfirmedCurrencyDetail;
        } else {
            confirmedCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);
        }
        if (retrievedConfirmedCoinDetail != null) {
            confirmedCoinDetail = retrievedConfirmedCoinDetail;
        } else {
            confirmedCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);
        }
        if (retrievedChangeCurrencyDetail != null) {
            changeCurrencyDetail = retrievedChangeCurrencyDetail;
        } else {
            changeCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_REQUEST);
        }
        if (retrievedChangeCoinDetail != null) {
            changeCoinDetail = retrievedChangeCoinDetail;
        } else {
            changeCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_REQUEST);
        }
        if (retrievedConfirmedChangeCurrencyDetail != null) {
            confirmedChangeCurrencyDetail = retrievedConfirmedChangeCurrencyDetail;
        } else {
            confirmedChangeCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
        }
        if (retrievedConfirmedChangeCoinDetail != null) {
            confirmedChangeCoinDetail = retrievedConfirmedChangeCoinDetail;
        } else {
            confirmedChangeCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
        }
    }

    /**
     * Get this document's currency detail from the database
     *
     * @return the currency detail record for this cash receipt document
     */
    protected CurrencyDetail retrieveCurrencyDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CurrencyDetail.class, getCashDetailPrimaryKey());
    }

    /**
     * Grab this document's coin detail from the database
     *
     * @return the coin detail record for this cash receipt document
     */
    protected CoinDetail retrieveCoinDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CoinDetail.class, getCashDetailPrimaryKey());
    }

    /**
     * Get this document's confirmed currency detail from the database
     *
     * @return the currency detail record for this cash receipt document
     */
    protected CurrencyDetail retrieveConfirmedCurrencyDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CurrencyDetail.class, getConfirmedCashDetailPrimaryKey());
    }

    /**
     * Get this document's confirmed coin detail from the database
     *
     * @return the coin detail record for this cash receipt document
     */
    protected CoinDetail retrieveConfirmedCoinDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CoinDetail.class, getConfirmedCashDetailPrimaryKey());
    }

    /**
     * Get this document's change currency detail from the database
     *
     * @return the change currency detail record for this cash receipt document
     */
    protected CurrencyDetail retrieveChangeCurrencyDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CurrencyDetail.class, getChangeCashDetailPrimaryKey());
    }

    /**
     * Get this document's change coin detail from the database
     *
     * @return the change coin detail record for this cash receipt document
     */
    protected CoinDetail retrieveChangeCoinDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CoinDetail.class, getChangeCashDetailPrimaryKey());
    }

    /**
     * Get this document's confirmed change currency detail from the database
     *
     * @return the change currency detail record for this cash receipt document
     */
    protected CurrencyDetail retrieveConfirmedChangeCurrencyDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CurrencyDetail.class, getConfirmedChangeCashDetailPrimaryKey());
    }

    /**
     * Get this document's confirmed change coin detail from the database
     *
     * @return the change coin detail record for this cash receipt document
     */
    protected CoinDetail retrieveConfirmedChangeCoinDetail() {
        return SpringContext.getBean(BusinessObjectService.class).findByPrimaryKey(CoinDetail.class, getConfirmedChangeCashDetailPrimaryKey());
    }

    /**
     * This method...
     *
     * @return
     */
    public String getCreateDate() {
        return SpringContext.getBean(DateTimeService.class).toDateString(getDocumentHeader().getWorkflowDocument().getDateCreated().toDate());
    }

    /**
     * Generate the primary key for a currency or coin detail related to this document
     *
     * @return a map with a representation of the proper primary key
     */
    protected Map getCashDetailPrimaryKey() {
        Map pk = new HashMap();
        pk.put("documentNumber", this.getDocumentNumber());
        pk.put("financialDocumentTypeCode", CashReceiptDocument.DOCUMENT_TYPE);
        pk.put("cashieringStatus", KFSConstants.CurrencyCoinSources.CASH_RECEIPTS);
        return pk;
    }

    protected Map getConfirmedCashDetailPrimaryKey() {
        Map pk = new HashMap();
        pk.put("documentNumber", this.getDocumentNumber());
        pk.put("financialDocumentTypeCode", CashReceiptDocument.DOCUMENT_TYPE);
        pk.put("cashieringStatus", KFSConstants.CurrencyCoinSources.CASH_MANAGEMENT_IN);
        return pk;
    }

    protected Map getChangeCashDetailPrimaryKey() {
        Map pk = new HashMap();
        pk.put("documentNumber", this.getDocumentNumber());
        pk.put("financialDocumentTypeCode", CashReceiptDocument.DOCUMENT_TYPE);
        pk.put("cashieringStatus", KFSConstants.CurrencyCoinSources.CASH_CHANGE_REQUEST);
        return pk;
    }

    protected Map getConfirmedChangeCashDetailPrimaryKey() {
        Map pk = new HashMap();
        pk.put("documentNumber", this.getDocumentNumber());
        pk.put("financialDocumentTypeCode", CashReceiptDocument.DOCUMENT_TYPE);
        pk.put("cashieringStatus", KFSConstants.CurrencyCoinSources.CASH_CHANGE_GRANTED);
        return pk;
    }

    /**
     * @see org.kuali.rice.kns.document.TransactionalDocumentBase#buildListOfDeletionAwareLists()
     */
    @Override
    public List buildListOfDeletionAwareLists() {
        List managedLists = super.buildListOfDeletionAwareLists();
        managedLists.add(getChecks());

        return managedLists;
    }

    @Override
    public List generateSaveEvents() {
        // 1. retrieve persisted checks for document
        // 2. retrieve current checks from given document
        // 3. compare, creating add/delete/update events as needed
        // 4. apply rules as appropriate returned events
        Collection<CheckBase> persistedChecks = SpringContext.getBean(CheckService.class).getByDocumentHeaderId(getDocumentNumber());
        List currentChecks = getChecks();

        List events = generateEvents(persistedChecks, currentChecks, KFSConstants.EXISTING_CHECK_PROPERTY_NAME, this);

        events.addAll(super.generateSaveEvents());

        return events;
    }

    /**
     * Generates a List of instances of CheckEvent subclasses, one for each changed check in the union of the persistedLines and
     * currentLines lists. Events in the list will be grouped in order by event-type (update, add, delete).
     *
     * @param persistedChecks
     * @param currentChecks
     * @param errorPathPrefix
     * @param crdoc
     * @return List of CheckEvent subclass instances
     */
    protected List generateEvents(Collection persistedChecks, List currentChecks, String errorPathPrefix, CashReceiptFamilyBase crdoc) {
        List addEvents = new ArrayList();
        List updateEvents = new ArrayList();
        List deleteEvents = new ArrayList();

        //
        // generate events
        Map persistedCheckMap = buildCheckMap(persistedChecks);

        // (iterate through current lines to detect additions and updates, removing affected lines from persistedLineMap as we go
        // so deletions can be detected by looking at whatever remains in persistedLineMap)
        int index = 0;
        for (Iterator i = currentChecks.iterator(); i.hasNext(); index++) {
            Check currentCheck = (Check) i.next();
            Integer key = currentCheck.getSequenceId();

            Check persistedCheck = (Check) persistedCheckMap.get(key);
            // if line is both current and persisted...
            if (persistedCheck != null) {
                // ...check for updates
                if (!currentCheck.isLike(persistedCheck)) {
                    UpdateCheckEvent updateEvent = new UpdateCheckEvent(errorPathPrefix, crdoc, currentCheck);
                    updateEvents.add(updateEvent);
                } else {
                    // do nothing, since this line hasn't changed
                }

                persistedCheckMap.remove(key);
            } else {
                // it must be a new addition
                AddCheckEvent addEvent = new AddCheckEvent(errorPathPrefix, crdoc, currentCheck);
                addEvents.add(addEvent);
            }
        }

        // detect deletions
        for (Iterator i = persistedCheckMap.entrySet().iterator(); i.hasNext(); ) {
            Map.Entry e = (Map.Entry) i.next();
            Check persistedCheck = (Check) e.getValue();
            DeleteCheckEvent deleteEvent = new DeleteCheckEvent(errorPathPrefix, crdoc, persistedCheck);
            deleteEvents.add(deleteEvent);
        }


        //
        // merge the lists
        List lineEvents = new ArrayList();
        lineEvents.addAll(updateEvents);
        lineEvents.addAll(addEvents);
        lineEvents.addAll(deleteEvents);

        return lineEvents;
    }


    /**
     * @param checks
     * @return Map containing Checks from the given List, indexed by their sequenceId
     */
    protected Map buildCheckMap(Collection checks) {
        Map checkMap = new HashMap();

        for (Iterator i = checks.iterator(); i.hasNext(); ) {
            Check check = (Check) i.next();
            Integer sequenceId = check.getSequenceId();

            Object oldCheck = checkMap.put(sequenceId, check);

            // verify that sequence numbers are unique...
            if (oldCheck != null) {
                throw new IllegalStateException("sequence id collision detected for sequence id " + sequenceId);
            }
        }

        return checkMap;
    }

    public Check createNewCheck() {
        Check newCheck = new CheckBase();
        newCheck.setFinancialDocumentTypeCode(DOCUMENT_TYPE);
        newCheck.setCashieringStatus(KFSConstants.CheckSources.CASH_RECEIPTS);
        return newCheck;
    }

    public Check createNewConfirmedCheck() {
        Check newCheck = new CheckBase();
        newCheck.setFinancialDocumentTypeCode(DOCUMENT_TYPE);
        newCheck.setCashieringStatus(KFSConstants.CheckSources.CASH_MANAGEMENT);
        return newCheck;
    }

    /**
     * Gets the depositCashReceiptControl attribute.
     *
     * @return Returns the depositCashReceiptControl.
     */
    public List getDepositCashReceiptControl() {
        return depositCashReceiptControl;
    }

    /**
     * Sets the depositCashReceiptControl attribute value.
     *
     * @param depositCashReceiptControl The depositCashReceiptControl to set.
     */
    public void setDepositCashReceiptControl(List depositCashReceiptControl) {
        this.depositCashReceiptControl = depositCashReceiptControl;
    }

    /**
     * Override the campus code on the copied document to whatever the campus of the copying user is
     *
     * @see org.kuali.kfs.sys.document.AccountingDocumentBase#toCopy()
     */
    @Override
    public void toCopy() throws WorkflowException {
        super.toCopy();

        /*
         * Currently copying a CR always copies the pre-verified cash details, even after CR is already verified.
         * It's probably more desirable to copy the confirmed details from the source CR to the new CR doc as original details,
         * after the source CR is verified.
         */
        initializeCampusLocationCode();

        if ((getChecks() == null || getChecks().isEmpty()) && getTotalCheckAmount().equals(KualiDecimal.ZERO)) {
            setCheckEntryMode(CashReceiptDocument.CHECK_ENTRY_DETAIL);
        }

        /* KFSMI-9914 (IU ref: FSKD-5275): Confirmed amounts were copied causing problems when copied doc was confirmed */
        setTotalConfirmedCheckAmount(KualiDecimal.ZERO);
        setTotalConfirmedCoinAmount(KualiDecimal.ZERO);
        totalChangeAmount = KualiDecimal.ZERO;
        confirmedChecks = new ArrayList<Check>();
        confirmedCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);
        confirmedCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_MANAGEMENT_IN);
        confirmedChangeCurrencyDetail = new CurrencyDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
        confirmedChangeCoinDetail = new CoinDetail(getDocumentNumber(), CashReceiptDocument.DOCUMENT_TYPE, CurrencyCoinSources.CASH_CHANGE_GRANTED);
    }

    /**
     * Initializes the campus location code based on kfs user role chart org
     */
    protected void initializeCampusLocationCode() {

        if (GlobalVariables.getUserSession() != null && GlobalVariables.getUserSession().getPerson() != null) {

            Person currentUser = GlobalVariables.getUserSession().getPerson();
            ChartOrgHolder chartOrg = SpringContext.getBean(org.kuali.kfs.sys.service.FinancialSystemUserService.class).getPrimaryOrganization(currentUser, KFSConstants.ParameterNamespaces.FINANCIAL);

            // Does a valid campus code exist for this person?  If so, simply grab
            // the campus code via the business object service.
            if (chartOrg != null && chartOrg.getOrganization() != null) {
                setCampusLocationCode(chartOrg.getOrganization().getOrganizationPhysicalCampusCode());
            }
            // A valid campus code was not found; therefore, use the default affiliated
            // campus code.
            else {
                String affiliatedCampusCode = currentUser.getCampusCode();
                setCampusLocationCode(affiliatedCampusCode);
            }
        }
    }

    /**
     * Deprecated: never used
     * Gets the sumTotalAmount attribute.
     *
     * @return Returns the sumTotalAmount
     */
    @Deprecated
    public KualiDecimal getSumTotalAmount() {
        return sumTotalAmount;
    }

    /**
     * Deprecated: never used
     * Sets the sumTotalAmount attribute.
     *
     * @param sumTotalAmount The sumTotalAmount to set.
     */
    @Deprecated
    public void setSumTotalAmount(KualiDecimal sumTotalAmount) {
        this.sumTotalAmount = sumTotalAmount;
    }


    public CurrencyDetail getConfirmedChangeCurrencyDetail() {
        return confirmedChangeCurrencyDetail;
    }

    public void setConfirmedChangeCurrencyDetail(CurrencyDetail confirmedChangeCurrencyDetail) {
        this.confirmedChangeCurrencyDetail = confirmedChangeCurrencyDetail;
    }

    public CoinDetail getConfirmedChangeCoinDetail() {
        return confirmedChangeCoinDetail;
    }

    public void setConfirmedChangeCoinDetail(CoinDetail confirmedChangeCoinDetail) {
        this.confirmedChangeCoinDetail = confirmedChangeCoinDetail;
    }

    /**
     * Gets the original (i.e. before Cash Manager adjustment) cash in total amount:
     * cash in = currency + coin;
     */
    public KualiDecimal getTotalCashInAmount() {
        return getTotalCurrencyAmount().add(getTotalCoinAmount());
    }

    /**
     * Gets the original (i.e. before Cash Manager adjustment) money in total amount:
     * money in = check + cash in;
     * cash in = currency + coin;
     */
    public KualiDecimal getTotalMoneyInAmount() {
        return getTotalCheckAmount().add(getTotalCashInAmount());
    }

    /**
     * Gets the original (i.e. before Cash Manager adjustment) reconciliation net total amount:
     * recon net = money in - money out = net deposit;
     * money in = check + currency + coin;
     * money out = change currency + change coin = change total.
     */
    public KualiDecimal getTotalNetAmount() {
        return getTotalMoneyInAmount().subtract(getTotalChangeAmount());
    }

    /**
     * Gets the total confirmed change currency amount.
     */
    public KualiDecimal getTotalConfirmedChangeCurrencyAmount() {
        return (confirmedChangeCurrencyDetail != null) ? confirmedChangeCurrencyDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the total confirmed change coin amount.
     */
    public KualiDecimal getTotalConfirmedChangeCoinAmount() {
        return (confirmedChangeCoinDetail != null) ? confirmedChangeCoinDetail.getTotalAmount() : KualiDecimal.ZERO;
    }

    /**
     * Gets the confirmed (i.e. after Cash Manager adjustment) change request (money out) total amount:
     * money out = change currency + change coin = change total.
     */
    public KualiDecimal getTotalConfirmedChangeAmount() {
        return getTotalConfirmedChangeCurrencyAmount().add(getTotalConfirmedChangeCoinAmount());
    }

    /**
     * Gets the confirmed (i.e. before Cash Manager adjustment) cash in total amount:
     * cash in = currency + coin;
     */
    public KualiDecimal getTotalConfirmedCashInAmount() {
        return getTotalConfirmedCurrencyAmount().add(getTotalConfirmedCoinAmount());
    }

    /**
     * Gets the confirmed (i.e. after Cash Manager adjustment) money in total amount:
     * money in = check + cash in;
     * cash in = currency + coin;
     */
    public KualiDecimal getTotalConfirmedMoneyInAmount() {
        return getTotalConfirmedCheckAmount().add(getTotalConfirmedCashInAmount());
    }

    /**
     * Gets the confirmed (i.e. after Cash Manager adjustment) net currency total amount:
     * net currency = currency - change currency
     */
    public KualiDecimal getTotalConfirmedNetCurrencyAmount() {
        return getTotalConfirmedCurrencyAmount().subtract(getTotalConfirmedChangeCurrencyAmount());
    }

    /**
     * Gets the confirmed (i.e. after Cash Manager adjustment) net coin total amount:
     * net coin = coin - change coin
     */
    public KualiDecimal getTotalConfirmedNetCoinAmount() {
        return getTotalConfirmedCoinAmount().subtract(getTotalConfirmedChangeCoinAmount());
    }

    /**
     * Gets the confirmed (i.e. after Cash Manager adjustment) net cash total amount:
     * net cash = net currency + net coin
     * net currency = currency - change currency
     * net coin = coin - change coin
     * or:
     * net cash = cash in - cash out
     * cash in = currency + coin;
     * cash out = change currency + change coin = change total.
     */
    public KualiDecimal getTotalConfirmedNetCashAmount() {
        return getTotalConfirmedNetCurrencyAmount().add(getTotalConfirmedNetCoinAmount());
    }

    /**
     * Gets the confirmed (i.e. after Cash Manager adjustment) reconciliation net total amount:
     * recon net = money in - money out = net deposit;
     * money in = check + currency + coin;
     * money out = change currency + change coin = change total.
     */
    public KualiDecimal getTotalConfirmedNetAmount() {
        return getTotalConfirmedMoneyInAmount().subtract(getTotalConfirmedChangeAmount());
    }

    /**
     * Returns true if the original change request has any field populated.
     */
    public boolean isOriginalChangeRequested() {
        return !changeCurrencyDetail.isEmpty() || !changeCoinDetail.isEmpty();
        //return getTotalChangeAmount().isNonZero();
    }

    /**
     * Returns true if the confirmed change request has any field populated.
     */
    public boolean isConfirmedChangeRequested() {
        return !confirmedChangeCurrencyDetail.isEmpty() || !confirmedChangeCoinDetail.isEmpty();
        //return getTotalConfirmedChangeAmount().isNonZero();
    }

    /**
     * Returns true if the change request tab is used, i.e. the original and/or the confirmed change request has any field populated.
     */
    public boolean isChangeRequested() {
        return isOriginalChangeRequested() || isConfirmedChangeRequested();
    }

    /**
     * Returns true if CR document status code is pre-route, i.e. it hasn't been routed for CashManager approval.
     * The impact is that, during this stage, any amount we use for actions (for ex, for validation or deposit)
     * should be pointing to the original one; otherwise, it should be pointing to the confirmed one.
     */
    public boolean isPreRoute() {
        // pre-route FinancialDocumentStatusCode should be INITIATED or CANCELLED; during CM routing the status is "R";
        // pre-route WorkflowDocumentStatusCode should be INITIATED, SAVED, or CANCELLED;
        // however, it's better to use Workflow status here, since if notes are added to the doc,
        // using the FinancialDocumentStatusCode won't be right.
        String statusCode = getFinancialSystemDocumentHeader().getWorkflowDocument().getStatus().getCode();
        boolean isPreRoute = DocumentStatus.INITIATED.getCode().equals(statusCode) ||
            DocumentStatus.SAVED.getCode().equals(statusCode) ||
            DocumentStatus.CANCELED.getCode().equals(statusCode);
        return isPreRoute;
    }

    /**
     * Returns true if CR document has been confirmed, i.e. approved by CashManagerstatus.
     * The impact is that, after CM confirmation, the confirmed checks and cash details won't be editable anymore,
     * but they still should be shown on the CR doc along with the original one.
     * Note that we don't include DISAPPROVED status here, since when CM disapproves the CR, CR doesn't get saved,
     * so no confirmed details will be saved anyways, thus no need to consider them.
     */
    public boolean isConfirmed() {
        // post CM approval status could be VERIFIED, INTERIM, FINAL, APPROVED
        String statusCode = getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode();
        boolean isConfirmed = CashReceipt.VERIFIED.equals(statusCode) || CashReceipt.INTERIM.equals(statusCode) ||
            CashReceipt.FINAL.equals(statusCode) || DocumentStatusCodes.APPROVED.equals(statusCode);
        return isConfirmed;
    }

    /**
     * Returns true if accounting line is debit
     *
     * @param financialDocument
     * @param accountingLine
     * @param true              if accountline line
     * @see IsDebitUtils#isDebitConsideringType(FinancialDocumentRuleBase, FinancialDocument, AccountingLine)
     * @see org.kuali.rice.krad.rule.AccountingLineRule#isDebit(org.kuali.rice.krad.document.FinancialDocument,
     * org.kuali.rice.krad.bo.AccountingLine)
     */
    @Override
    public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) {
        // error corrections are not allowed on CR
        SpringContext.getBean(DebitDeterminerService.class).disallowErrorCorrectionDocumentCheck(this);
        return super.isDebit(postable);
    }

    /**
     * Provides answers to the following splits: AcknowledgeChangeRequest
     *
     * @returns true if change request is used, false otherwise.
     * @see org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase#answerSplitNodeQuestion(java.lang.String)
     */
    @Override
    public boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException {
        if (nodeName.equals(REQUIRE_REVIEW_SPLIT)) {
            return isChangeRequested();
        }
        throw new UnsupportedOperationException("Cannot answer split question for this node you call \"" + nodeName + "\"");
    }
}

