/*
 * 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.web.struts;

import org.apache.commons.lang.StringUtils;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.kuali.kfs.fp.businessobject.CashDrawer;
import org.kuali.kfs.fp.businessobject.CashieringTransaction;
import org.kuali.kfs.fp.businessobject.Check;
import org.kuali.kfs.fp.businessobject.CoinDetail;
import org.kuali.kfs.fp.businessobject.CurrencyDetail;
import org.kuali.kfs.fp.businessobject.Deposit;
import org.kuali.kfs.fp.businessobject.DepositWizardCashieringCheckHelper;
import org.kuali.kfs.fp.businessobject.DepositWizardHelper;
import org.kuali.kfs.fp.businessobject.format.CashDrawerStatusCodeFormatter;
import org.kuali.kfs.fp.document.CashManagementDocument;
import org.kuali.kfs.fp.document.CashReceiptDocument;
import org.kuali.kfs.fp.document.service.CashManagementService;
import org.kuali.kfs.fp.document.service.CashReceiptService;
import org.kuali.kfs.fp.exception.CashDrawerStateException;
import org.kuali.kfs.fp.service.CashDrawerService;
import org.kuali.kfs.kns.document.authorization.DocumentAuthorizer;
import org.kuali.kfs.kns.document.authorization.TransactionalDocumentAuthorizer;
import org.kuali.kfs.kns.document.authorization.TransactionalDocumentPresentationController;
import org.kuali.kfs.kns.service.DataDictionaryService;
import org.kuali.kfs.kns.service.DocumentHelperService;
import org.kuali.kfs.kns.web.struts.action.KualiAction;
import org.kuali.kfs.krad.exception.InfrastructureException;
import org.kuali.kfs.krad.service.BusinessObjectService;
import org.kuali.kfs.krad.service.DocumentService;
import org.kuali.kfs.krad.util.GlobalVariables;
import org.kuali.kfs.krad.util.KRADConstants;
import org.kuali.kfs.krad.util.UrlFactory;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSConstants.CashDrawerConstants;
import org.kuali.kfs.sys.KFSConstants.DepositConstants;
import org.kuali.kfs.sys.KFSConstants.DocumentStatusCodes.CashReceipt;
import org.kuali.kfs.sys.KFSKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
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.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * This class handles actions for the deposit wizard, which is used to create deposits that bundle groupings of Cash Receipt
 * documents.
 */
public class DepositWizardAction extends KualiAction {
    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DepositWizardAction.class);
    private static final String CASH_MANAGEMENT_STATUS_PAGE = "/cashManagementStatus.do";

    /**
     * Overrides the parent to validate the document state of the cashManagementDocument which will be updated and redisplayed after
     * the DepositWizard builds and attaches the new Deposit.
     *
     * @see org.apache.struts.action.Action#execute(org.apache.struts.action.ActionMapping, org.apache.struts.action.ActionForm,
     * javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        DepositWizardForm dwForm = (DepositWizardForm) form;

        ActionForward dest = super.execute(mapping, form, request, response);

        // check authorization manually, since the auth-check isn't inherited by this class
        DocumentAuthorizer cmDocAuthorizer = SpringContext.getBean(DocumentHelperService.class).getDocumentAuthorizer(KFSConstants.FinancialDocumentTypeCodes.CASH_MANAGEMENT);
        Person luser = GlobalVariables.getUserSession().getPerson();
        cmDocAuthorizer.canInitiate(KFSConstants.FinancialDocumentTypeCodes.CASH_MANAGEMENT, luser);

        // populate the outgoing form used by the JSP if it seems empty
        String cmDocId = dwForm.getCashManagementDocId();
        if (StringUtils.isBlank(cmDocId)) {
            cmDocId = request.getParameter("cmDocId");
            String depositTypeCode = request.getParameter("depositTypeCode");

            CashManagementDocument cmDoc = (CashManagementDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(cmDocId);

            try {
                initializeForm(dwForm, cmDoc, depositTypeCode);
            } catch (CashDrawerStateException cdse) {
                dest = new ActionForward(UrlFactory.parameterizeUrl(CASH_MANAGEMENT_STATUS_PAGE, cdse.toProperties()), true);
            }
        } else { // for recalculation
            loadCashReceipts(dwForm);
            loadUndepositedCashieringChecks(dwForm);
            if (dwForm.getTargetDepositAmount() == null) {
                calculateTargetFinalDepositAmount(dwForm);
            }
            loadEditModesAndDocumentActions(dwForm);
        }

        return dest;
    }

    /**
     * Initializes the given form using the given values
     *
     * @param dform
     * @param cmDoc
     * @param depositTypeCode
     */
    private void initializeForm(DepositWizardForm dform, CashManagementDocument cmDoc, String depositTypeCode) {
        CashDrawer cd = SpringContext.getBean(CashDrawerService.class).getByCampusCode(cmDoc.getCampusCode());
        if (cd == null) {
            throw new RuntimeException("No cash drawer exists for campus code " + cmDoc.getCampusCode() + "; please create on via the Cash Drawer Maintenance Document before attemping to create a CashManagementDocument for campus " + cmDoc.getCampusCode());
        }
        if (!cd.isOpen()) {
            CashDrawerStatusCodeFormatter f = new CashDrawerStatusCodeFormatter();

            String cmDocId = cmDoc.getDocumentNumber();
            String currentState = cd.getStatusCode();

            throw new CashDrawerStateException(cmDoc.getCampusCode(), cmDocId, (String) f.format(CashDrawerConstants.STATUS_OPEN), (String) f.format(cd.getStatusCode()));
        }

        dform.setCashManagementDocId(cmDoc.getDocumentNumber());
        dform.setCashDrawerCampusCode(cmDoc.getCampusCode());

        dform.setDepositTypeCode(depositTypeCode);

        if (depositTypeCode.equals(DepositConstants.DEPOSIT_TYPE_FINAL)) {
            // hey, we're the magical final deposit. We get currency and coin details!
            CurrencyDetail currencyDetail = new CurrencyDetail();
            currencyDetail.setDocumentNumber(cmDoc.getDocumentNumber());
            currencyDetail.setCashieringStatus(KFSConstants.CurrencyCoinSources.DEPOSITS);
            currencyDetail.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
            dform.setCurrencyDetail(currencyDetail);

            CoinDetail coinDetail = new CoinDetail();
            coinDetail.setDocumentNumber(cmDoc.getDocumentNumber());
            coinDetail.setCashieringStatus(KFSConstants.CurrencyCoinSources.DEPOSITS);
            coinDetail.setFinancialDocumentTypeCode(CashieringTransaction.DETAIL_DOCUMENT_TYPE);
            dform.setCoinDetail(coinDetail);
        }

        loadCashReceipts(dform);
        loadUndepositedCashieringChecks(dform);
        if (dform.isDepositFinal() && dform.getTargetDepositAmount() == null) {
            calculateTargetFinalDepositAmount(dform);
        }
        loadEditModesAndDocumentActions(dform);
    }

    /**
     * Loads the CashReceipt information, re/setting the related form fields
     *
     * @param dform
     */
    private void loadCashReceipts(DepositWizardForm dform) {
        List<CashReceiptDocument> verifiedReceipts = SpringContext.getBean(CashReceiptService.class).getCashReceipts(dform.getCashDrawerCampusCode(), new String[]{CashReceipt.VERIFIED, CashReceipt.INTERIM});
        dform.setDepositableCashReceipts(new ArrayList());
        dform.setCheckFreeCashReceipts(new ArrayList<CashReceiptDocument>());
        dform.resetCurrentCheckTotal();

        // prepopulate DepositWizardHelpers
        int index = 0;
        for (Iterator i = verifiedReceipts.iterator(); i.hasNext(); ) {
            CashReceiptDocument receipt = (CashReceiptDocument) i.next();
            receipt.processAfterRetrieve(); // To populate Currency and Coin details
            String docStatus = receipt.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode();
            if (docStatus.equalsIgnoreCase(CashReceipt.VERIFIED)) { // for interim or final deposit
                if (receipt.getCheckCount() == 0 && receipt.getTotalConfirmedCheckAmount().equals(KualiDecimal.ZERO)) {
                    dform.getCheckFreeCashReceipts().add(receipt);
                } else {
                    dform.getDepositableCashReceipts().add(receipt);
                    DepositWizardHelper d = dform.getDepositWizardHelper(index++);
                    // KFSMI-5232 Jira fix. Convert the time stamp to SQL date format
                    Timestamp ts = new Timestamp(receipt.getDocumentHeader().getWorkflowDocument().getDateCreated().getMillis());
                    try {
                        d.setCashReceiptCreateDate(SpringContext.getBean(DateTimeService.class).convertToSqlDate(ts));
                    } catch (Exception e) {

                    }
                }
            } else if (docStatus.equalsIgnoreCase(CashReceipt.INTERIM)) { // for final deposit
                // checks are already deposited but there are cash to be deposited
                /* We should check whether the net cash total, i.e. (currency + coin) - (change currency + change coin)
                 * is greater than 0; and if so, there's cash to deposit.
                 */
                if (receipt.getTotalConfirmedNetCashAmount().isGreaterThan(KualiDecimal.ZERO)) {
                    dform.getCheckFreeCashReceipts().add(receipt);
                }
            }
            dform.addCashReceiptToChecks(receipt);
        }
    }

    /**
     * Loads cashiering transactions for final deposit target amount
     *
     * @param dwform
     */
    private void calculateTargetFinalDepositAmount(DepositWizardForm dwform) {
        final CashReceiptService cashReceiptService = SpringContext.getBean(CashReceiptService.class);
        final List<CashReceiptDocument> interestingReceipts =
            cashReceiptService.getCashReceipts(dwform.getCashDrawerCampusCode(), new String[]{CashReceipt.VERIFIED, CashReceipt.INTERIM, CashReceipt.FINAL});
        for (CashReceiptDocument crDoc : interestingReceipts) {
            crDoc.refreshCashDetails();
            dwform.addCashReceiptToTargetTotal(crDoc);
        }

        final CashManagementService cashManagementService = SpringContext.getBean(CashManagementService.class);
        KualiDecimal toBeDepositedChecksTotal = KualiDecimal.ZERO;
        for (Check check : cashManagementService.selectUndepositedCashieringChecks(dwform.getCashManagementDocId())) {
            toBeDepositedChecksTotal = toBeDepositedChecksTotal.add(check.getAmount());
        }
        // since final, include deposited checks as well (see KFSCNTRB-160)
        for (Check check : cashManagementService.selectDepositedCashieringChecks(dwform.getCashManagementDocId())) {
            toBeDepositedChecksTotal = toBeDepositedChecksTotal.add(check.getAmount());
        }
        dwform.addCashieringTransactionToTargetTotal(toBeDepositedChecksTotal);
    }

    /**
     * This loads any cashiering checks which have not yet been deposited into the DepositWizardForm
     *
     * @param dform a form to load undeposited checks into
     */
    private void loadUndepositedCashieringChecks(DepositWizardForm dform) {
        List<Check> cashieringChecks = SpringContext.getBean(CashManagementService.class).selectUndepositedCashieringChecks(dform.getCashManagementDocId());
        dform.setDepositableCashieringChecks(cashieringChecks);
        dform.addCashieringReceiptToChecks();
    }

    private void loadEditModesAndDocumentActions(DepositWizardForm dform) {
        final FinancialSystemTransactionalDocumentEntry ddEntry = getCashManagementDataDictionaryEntry();
        final TransactionalDocumentPresentationController presentationController = getCashManagementPresentationController(ddEntry);
        final TransactionalDocumentAuthorizer docAuthorizer = getCashManagementDocumentAuthorizer(ddEntry);

        dform.setEditingMode(retrieveEditingModes(dform.getCashManagementDocId(), presentationController, docAuthorizer));
        dform.setDocumentActions(retrieveDocumentActions(dform.getCashManagementDocId(), presentationController, docAuthorizer));
    }

    /**
     * @return the class of the cash management document
     */
    protected String getCashManagementDocumentTypeName() {
        return "CMD";
    }

    /**
     * @return the data dictionary entry for the cash management class
     */
    private FinancialSystemTransactionalDocumentEntry getCashManagementDataDictionaryEntry() {
        final DataDictionaryService ddService = SpringContext.getBean(DataDictionaryService.class);
        return (FinancialSystemTransactionalDocumentEntry) ddService.getDataDictionary().getDocumentEntry(getCashManagementDocumentTypeName());
    }

    /**
     * Returns an instance of the document presentation controller for the cash management class
     *
     * @param cashManagementEntry the data dictionary entry for the cash management document
     * @return an instance of the proper document presentation controller
     */
    private TransactionalDocumentPresentationController getCashManagementPresentationController(FinancialSystemTransactionalDocumentEntry cashManagementEntry) {
        final Class presentationControllerClass = cashManagementEntry.getDocumentPresentationControllerClass();
        TransactionalDocumentPresentationController presentationController = null;
        try {
            presentationController = (TransactionalDocumentPresentationController) presentationControllerClass.newInstance();
        } catch (InstantiationException ie) {
            throw new RuntimeException("Could not instantiate cash management presentation controller of class " + presentationControllerClass.getName(), ie);
        } catch (IllegalAccessException iae) {
            throw new RuntimeException("Could not instantiate cash management presentation controller of class " + presentationControllerClass.getName(), iae);
        }
        return presentationController;
    }

    /**
     * Returns an instance of the document authorizer for the cash management class
     *
     * @param cashManagementEntry the data dictionary entry for the cash management document
     * @return an instance of the proper document authorizer
     */
    private TransactionalDocumentAuthorizer getCashManagementDocumentAuthorizer(FinancialSystemTransactionalDocumentEntry cashManagementEntry) {
        final Class docAuthorizerClass = cashManagementEntry.getDocumentAuthorizerClass();
        TransactionalDocumentAuthorizer docAuthorizer = null;
        try {
            docAuthorizer = (TransactionalDocumentAuthorizer) docAuthorizerClass.newInstance();
        } catch (InstantiationException ie) {
            throw new RuntimeException("Could not instantiate cash management document authorizer of class " + docAuthorizerClass.getName(), ie);
        } catch (IllegalAccessException iae) {
            throw new RuntimeException("Could not instantiate cash management document authorizer of class " + docAuthorizerClass.getName(), iae);
        }
        return docAuthorizer;
    }

    /**
     * Retrieves the edit modes for the given cash management document
     *
     * @param cashManagementDocId    the id of the cash management document to check
     * @param presentationController the presentation controller of the cash management document
     * @param docAuthorizer          the cash management document authorizer
     * @return a Map of edit modes
     */
    private Map retrieveEditingModes(String cashManagementDocId, TransactionalDocumentPresentationController presentationController, TransactionalDocumentAuthorizer docAuthorizer) {
        Map editModeMap = null;
        try {
            final CashManagementDocument cmDoc = (CashManagementDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(cashManagementDocId);
            Set<String> editModes = presentationController.getEditModes(cmDoc);
            editModes = docAuthorizer.getEditModes(cmDoc, GlobalVariables.getUserSession().getPerson(), editModes);
            editModeMap = convertSetToMap(editModes);
        } catch (WorkflowException we) {
            throw new RuntimeException("Workflow exception while retrieving document " + cashManagementDocId, we);
        }
        return editModeMap;
    }

    /**
     * Retrieves the document actions for the given cash management document
     *
     * @param cashManagementDocId    the id of the cash management document to check
     * @param presentationController the presentation controller of the cash management document
     * @param docAuthorizer          the cash management document authorizer
     * @return a Map of document actions
     */
    private Map retrieveDocumentActions(String cashManagementDocId, TransactionalDocumentPresentationController presentationController, TransactionalDocumentAuthorizer docAuthorizer) {
        Map documentActionsMap = null;
        try {
            final CashManagementDocument cmDoc = (CashManagementDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(cashManagementDocId);
            Set<String> documentActions = presentationController.getDocumentActions(cmDoc);
            documentActions = docAuthorizer.getEditModes(cmDoc, GlobalVariables.getUserSession().getPerson(), documentActions);
            documentActionsMap = convertSetToMap(documentActions);
        } catch (WorkflowException we) {
            throw new RuntimeException("Workflow exception while retrieving document " + cashManagementDocId, we);
        }
        return documentActionsMap;
    }

    /**
     * Converts a set into a map, where each value in the set becomes a key and each value becomes KNSConstants.KUALI_DEFAULT_TRUE_VALUE
     *
     * @param s a set
     * @return a map
     */
    protected Map convertSetToMap(Set s) {
        Map map = new HashMap();
        Iterator i = s.iterator();
        while (i.hasNext()) {
            Object key = i.next();
            map.put(key, KRADConstants.KUALI_DEFAULT_TRUE_VALUE);
        }
        return map;
    }

    /**
     * Reloads the CashReceipts, leaving everything else unchanged
     *
     * @see org.kuali.rice.kns.web.struts.action.KualiAction#refresh(org.apache.struts.action.ActionMapping,
     * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public ActionForward refresh(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        loadCashReceipts((DepositWizardForm) form);
        loadUndepositedCashieringChecks((DepositWizardForm) form);
        if (((DepositWizardForm) form).isDepositFinal() && ((DepositWizardForm) form).getTargetDepositAmount() == null) {
            calculateTargetFinalDepositAmount((DepositWizardForm) form);
        }
        loadEditModesAndDocumentActions((DepositWizardForm) form);

        return super.refresh(mapping, form, request, response);
    }

    /**
     * This method is the starting point for the deposit document wizard.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return ActionForward
     * @throws Exception
     */
    public ActionForward startWizard(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        return mapping.findForward(KFSConstants.MAPPING_BASIC);
    }

    /**
     * This method is the action method for creating the new deposit document from the information chosen by the user in the UI.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return ActionForward
     */
    public ActionForward createDeposit(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) {
        ActionForward dest = mapping.findForward(KFSConstants.MAPPING_BASIC);

        DepositWizardForm dform = (DepositWizardForm) form;
        final BusinessObjectService boService = SpringContext.getBean(BusinessObjectService.class);
        final CashReceiptService cashReceiptService = SpringContext.getBean(CashReceiptService.class);
        final DocumentService documentService = SpringContext.getBean(DocumentService.class);
        final CashManagementService cashManagementService = SpringContext.getBean(CashManagementService.class);

        CurrencyFormatter formatter = new CurrencyFormatter();

        // reload edit modes and summary totals - just in case we have to return to the deposit wizard page
        loadCashReceipts((DepositWizardForm) form);
        if (((DepositWizardForm) form).isDepositFinal() && ((DepositWizardForm) form).getTargetDepositAmount() == null) {
            calculateTargetFinalDepositAmount((DepositWizardForm) form);
        }
        loadEditModesAndDocumentActions(dform);

        // validate Bank
        String bankCode = dform.getBankCode();
        if (StringUtils.isBlank(bankCode)) {
            GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_MISSING_BANK);
        } else {
            Map keyMap = new HashMap();
            keyMap.put(KFSPropertyConstants.BANK_CODE, bankCode);

            Bank bank = boService.findByPrimaryKey(Bank.class, keyMap);
            if (bank == null) {
                GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_UNKNOWN_BANK, bankCode);
            } else {
                dform.setBank(bank);
            }
        }

        boolean depositIsFinal = (StringUtils.equals(dform.getDepositTypeCode(), KFSConstants.DepositConstants.DEPOSIT_TYPE_FINAL));

        // validate cashReceipt selection
        List selectedIds = new ArrayList();
        for (Iterator i = dform.getDepositWizardHelpers().iterator(); i.hasNext(); ) {
            String checkValue = ((DepositWizardHelper) i.next()).getSelectedValue();

            if (StringUtils.isNotBlank(checkValue) && !checkValue.equals(KFSConstants.ParameterValues.NO)) {
                // removed apparently-unnecessary test for !checkValue.equals(KFSConstants.ParameterValues.YES)
                selectedIds.add(checkValue);
            }
        }

        if (depositIsFinal) {
            // add check free cash receipts to the selected receipts so they are automatically deposited
            dform.setCheckFreeCashReceipts(new ArrayList<CashReceiptDocument>());
            List<CashReceiptDocument> cashReceipts = cashReceiptService.getCashReceipts(dform.getCashDrawerCampusCode(), new String[]{CashReceipt.VERIFIED, CashReceipt.INTERIM});
            for (Object crDocObj : cashReceipts) {
                CashReceiptDocument crDoc = (CashReceiptDocument) crDocObj;
                crDoc.refreshCashDetails();
                if (crDoc.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode().equals(CashReceipt.VERIFIED) && crDoc.getCheckCount() == 0) {
                    // it's check free; it is automatically deposited as part of the final deposit
                    selectedIds.add(crDoc.getDocumentNumber());
                    dform.getCheckFreeCashReceipts().add(crDoc);
                } else if (crDoc.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode().equals(CashReceipt.INTERIM) &&
                    crDoc.getTotalConfirmedNetAmount().isGreaterThan(KualiDecimal.ZERO)) {
                    selectedIds.add(crDoc.getDocumentNumber());
                    dform.getCheckFreeCashReceipts().add(crDoc);
                }
            }
        }

        // make a list of cashiering checks to deposit
        List<Integer> selectedCashieringChecks = new ArrayList<Integer>();
        for (DepositWizardCashieringCheckHelper helper : dform.getDepositWizardCashieringCheckHelpers()) {
            if (helper.getSequenceId() != null && !helper.getSequenceId().equals(new Integer(-1))) {
                selectedCashieringChecks.add(helper.getSequenceId());
            }
        }

        if (selectedIds.isEmpty() && selectedCashieringChecks.isEmpty()) {
            GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_CASHRECEIPT_ERROR, KFSKeyConstants.Deposit.ERROR_NO_CASH_RECEIPTS_SELECTED);
        }

        //
        // proceed, if possible
        if (GlobalVariables.getMessageMap().hasNoErrors()) {
            try {
                // retrieve selected receipts
                List selectedReceipts = new ArrayList();
                if (selectedIds != null && !selectedIds.isEmpty()) {
                    selectedReceipts = documentService.getDocumentsByListOfDocumentHeaderIds(CashReceiptDocument.class, selectedIds);
                }

                if (depositIsFinal) {
                    // have all verified CRs been deposited? If not, that's an error
//                    List verifiedReceipts = cashReceiptService.getCashReceipts(dform.getCashDrawerCampusCode(), new String[] {CashReceipt.VERIFIED, CashReceipt.INTERIM});
//                    for (Object o : verifiedReceipts) {
//                        CashReceiptDocument crDoc = (CashReceiptDocument) o;
//                        if (!selectedReceipts.contains(crDoc)) {
//                            GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NON_DEPOSITED_VERIFIED_CASH_RECEIPT, new String[] { crDoc.getDocumentNumber() });
//                        }
//                    }
                    KualiDecimal toBeDepositedChecksTotal = KualiDecimal.ZERO;
                    // have we selected the rest of the undeposited checks?
                    for (Check check : cashManagementService.selectUndepositedCashieringChecks(dform.getCashManagementDocId())) {
                        if (!selectedCashieringChecks.contains(check.getSequenceId())) {
                            GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_CASHIERING_CHECK_MUST_BE_DEPOSITED, new String[]{check.getCheckNumber()});
                        } else {
                            toBeDepositedChecksTotal = toBeDepositedChecksTotal.add(check.getAmount());
                        }
                    }
                    // add the rest of the deposited cashiering checks to the toBeDepositedChecksTotal
                    for (Check check : cashManagementService.selectDepositedCashieringChecks(dform.getCashManagementDocId())) {
                        toBeDepositedChecksTotal = toBeDepositedChecksTotal.add(check.getAmount());
                    }

                    // does the cash drawer have enough currency and coin to fulfill the requested deposit?
                    checkEnoughCurrencyForDeposit(dform);
                    checkEnoughCoinForDeposit(dform);

                    // does this deposit have currency and coin to match all currency and coin from CRs?
                    List<CashReceiptDocument> interestingReceipts = cashReceiptService.getCashReceipts(dform.getCashDrawerCampusCode(), new String[]{CashReceipt.VERIFIED, CashReceipt.INTERIM, CashReceipt.FINAL});
                    CurrencyDetail currencyTotal = new CurrencyDetail();
                    CoinDetail coinTotal = new CoinDetail();
                    for (CashReceiptDocument receipt : interestingReceipts) {
                        receipt.refreshCashDetails();
                        /* We should check on confirmed details instead of original ones;
                         * we don't really need to check null here, since these details won't be null after refreshCashDetails.
                         */
                        currencyTotal.add(receipt.getConfirmedCurrencyDetail());
                        currencyTotal.subtract(receipt.getConfirmedChangeCurrencyDetail());
                        coinTotal.add(receipt.getConfirmedCoinDetail());
                        coinTotal.subtract(receipt.getConfirmedChangeCoinDetail());
                    }

                    KualiDecimal cashReceiptCashTotal = currencyTotal.getTotalAmount().add(coinTotal.getTotalAmount());
                    // remove the cashiering checks amounts from the cash receipts total; cashiering checks act as if they were CR
                    // currency/coin that gets deposited
                    cashReceiptCashTotal = cashReceiptCashTotal.subtract(toBeDepositedChecksTotal);
                    KualiDecimal depositedCashTotal = dform.getCurrencyDetail().getTotalAmount().add(dform.getCoinDetail().getTotalAmount());
                    if (!cashReceiptCashTotal.equals(depositedCashTotal)) {
                        GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_CASH_DEPOSIT_DID_NOT_BALANCE, new String[]{formatter.format(depositedCashTotal).toString(), formatter.format(cashReceiptCashTotal).toString()});
                    }
                }

                // proceed again...if possible
                if (GlobalVariables.getMessageMap().hasNoErrors()) {
                    // retrieve CashManagementDocument
                    String cashManagementDocId = dform.getCashManagementDocId();
                    CashManagementDocument cashManagementDoc = null;
                    try {
                        cashManagementDoc = (CashManagementDocument) documentService.getByDocumentHeaderId(cashManagementDocId);
                        if (cashManagementDoc == null) {
                            throw new IllegalStateException("unable to find cashManagementDocument with id " + cashManagementDocId);
                        }
                    } catch (WorkflowException e) {
                        throw new IllegalStateException("unable to retrieve cashManagementDocument with id " + cashManagementDocId, e);
                    }

                    // create deposit
                    String cmDocId = dform.getCashManagementDocId();

                    cashManagementService.addDeposit(cashManagementDoc, dform.getDepositTicketNumber(), dform.getBank(), selectedReceipts, selectedCashieringChecks, depositIsFinal);

                    if (depositIsFinal) {
                        // find the final deposit
                        Deposit finalDeposit = findFinalDeposit(cashManagementDoc);
                        // if the currency and coin details aren't empty, save them and remove them from the cash drawer
                        if (dform.getCurrencyDetail() != null) {
                            // do we have enough currency to allow the deposit to leave the drawer?
                            boService.save(dform.getCurrencyDetail());
                            cashManagementDoc.getCashDrawer().removeCurrency(dform.getCurrencyDetail());
                            finalDeposit.setDepositAmount(finalDeposit.getDepositAmount().add(dform.getCurrencyDetail().getTotalAmount()));
                        }
                        if (dform.getCoinDetail() != null) {
                            // do we have enough coin to allow the deposit to leave the drawer?
                            boService.save(dform.getCoinDetail());
                            cashManagementDoc.getCashDrawer().removeCoin(dform.getCoinDetail());
                            finalDeposit.setDepositAmount(finalDeposit.getDepositAmount().add(dform.getCoinDetail().getTotalAmount()));
                        }
                        boService.save(cashManagementDoc.getCashDrawer());
                        boService.save(finalDeposit);
                    }

                    // redirect to controlling CashManagementDocument
                    dest = returnToSender(cashManagementDocId);
                }
            } catch (WorkflowException e) {
                throw new InfrastructureException("unable to retrieve cashReceipts by documentId", e);
            }
        }

        return dest;
    }

    private Deposit findFinalDeposit(CashManagementDocument cmDoc) {
        Deposit finalDeposit = null;
        for (Deposit deposit : cmDoc.getDeposits()) {
            if (deposit.getDepositTypeCode().equals(KFSConstants.DepositConstants.DEPOSIT_TYPE_FINAL)) {
                finalDeposit = deposit;
                break;
            }
        }
        return finalDeposit;
    }

    /**
     * Checks that the currency amount requested to be part of a deposit can be fulfilled by the amount of currency in the cash
     * drawer
     *
     * @param depositForm the deposit form we are checking against
     * @param detail      the currency detail to check against the drawer
     * @return true if enough currency, false if otherwise
     */
    private boolean checkEnoughCurrencyForDeposit(DepositWizardForm depositForm) {
        boolean success = true;
        CurrencyDetail detail = depositForm.getCurrencyDetail();
        if (detail != null) {
            // 1. get the cash drawer
            CashDrawer drawer = SpringContext.getBean(CashDrawerService.class).getByCampusCode(depositForm.getCashDrawerCampusCode());
            // assumptions at this point:
            // 1. a cash drawer does exist for the unit
            // 2. we can ignore negative amounts, because if we have negative amounts, we're actually gaining money (and that will
            // happen with cashiering checks)
            CurrencyFormatter formatter = new CurrencyFormatter();
            if (detail.getFinancialDocumentHundredDollarAmount() != null && detail.getFinancialDocumentHundredDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentHundredDollarAmount() == null || drawer.getFinancialDocumentHundredDollarAmount().isLessThan(detail.getFinancialDocumentHundredDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"hundred dollar amount", formatter.format(detail.getFinancialDocumentHundredDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentHundredDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentFiftyDollarAmount() != null && detail.getFinancialDocumentFiftyDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentFiftyDollarAmount() == null || drawer.getFinancialDocumentFiftyDollarAmount().isLessThan(detail.getFinancialDocumentFiftyDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"fifty dollar amount", formatter.format(detail.getFinancialDocumentFiftyDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentFiftyDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentTwentyDollarAmount() != null && detail.getFinancialDocumentTwentyDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentTwentyDollarAmount() == null || drawer.getFinancialDocumentTwentyDollarAmount().isLessThan(detail.getFinancialDocumentTwentyDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"twenty dollar amount", formatter.format(detail.getFinancialDocumentTwentyDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentTwentyDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentTenDollarAmount() != null && detail.getFinancialDocumentTenDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentTenDollarAmount() == null || drawer.getFinancialDocumentTenDollarAmount().isLessThan(detail.getFinancialDocumentTenDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"ten dollar amount", formatter.format(detail.getFinancialDocumentTenDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentTenDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentFiveDollarAmount() != null && detail.getFinancialDocumentFiveDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentFiveDollarAmount() == null || drawer.getFinancialDocumentFiveDollarAmount().isLessThan(detail.getFinancialDocumentFiveDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"five dollar amount", formatter.format(detail.getFinancialDocumentFiveDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentFiveDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentTwoDollarAmount() != null && detail.getFinancialDocumentTwoDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentTwoDollarAmount() == null || drawer.getFinancialDocumentTwoDollarAmount().isLessThan(detail.getFinancialDocumentTwoDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"two dollar amount", formatter.format(detail.getFinancialDocumentTwoDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentTwoDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentOneDollarAmount() != null && detail.getFinancialDocumentOneDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentOneDollarAmount() == null || drawer.getFinancialDocumentOneDollarAmount().isLessThan(detail.getFinancialDocumentOneDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"one dollar amount", formatter.format(detail.getFinancialDocumentOneDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentOneDollarAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentOtherDollarAmount() != null && detail.getFinancialDocumentOtherDollarAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentOtherDollarAmount() == null || drawer.getFinancialDocumentOtherDollarAmount().isLessThan(detail.getFinancialDocumentOtherDollarAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"other dollar amount", formatter.format(detail.getFinancialDocumentOtherDollarAmount()).toString(), formatter.format(drawer.getFinancialDocumentOtherDollarAmount()).toString()});
                    success = false;
                }
            }
        }
        return success;
    }

    /**
     * Checks that the coin amount requested by the deposit does not exceed the amount actually in the drawer
     *
     * @param depositForm the deposit form we are checking against
     * @param detail      the coin detail to check against the drawer
     * @return true if there is enough coin, false if otherwise
     */
    public boolean checkEnoughCoinForDeposit(DepositWizardForm depositForm) {
        boolean success = true;
        CoinDetail detail = depositForm.getCoinDetail();
        if (detail != null) {
            // 1. get the cash drawer
            CashDrawer drawer = SpringContext.getBean(CashDrawerService.class).getByCampusCode(depositForm.getCashDrawerCampusCode());
            // assumptions at this point:
            // 1. a cash drawer does exist for the unit
            // 2. we can ignore negative amounts, because if we have negative amounts, we're actually gaining money (and that will
            // happen with cashiering checks)
            CurrencyFormatter formatter = new CurrencyFormatter();
            if (detail.getFinancialDocumentHundredCentAmount() != null && detail.getFinancialDocumentHundredCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentHundredCentAmount() == null || drawer.getFinancialDocumentHundredCentAmount().isLessThan(detail.getFinancialDocumentHundredCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"hundred cent amount", formatter.format(detail.getFinancialDocumentHundredCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentHundredCentAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentFiftyCentAmount() != null && detail.getFinancialDocumentFiftyCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentFiftyCentAmount() == null || drawer.getFinancialDocumentFiftyCentAmount().isLessThan(detail.getFinancialDocumentFiftyCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"fifty cent amount", formatter.format(detail.getFinancialDocumentFiftyCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentFiftyCentAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentTwentyFiveCentAmount() != null && detail.getFinancialDocumentTwentyFiveCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentTwentyFiveCentAmount() == null || drawer.getFinancialDocumentTwentyFiveCentAmount().isLessThan(detail.getFinancialDocumentTwentyFiveCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"twenty five cent amount", formatter.format(detail.getFinancialDocumentTwentyFiveCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentTwentyFiveCentAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentTenCentAmount() != null && detail.getFinancialDocumentTenCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentTenCentAmount() == null || drawer.getFinancialDocumentTenCentAmount().isLessThan(detail.getFinancialDocumentTenCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"ten cent amount", formatter.format(detail.getFinancialDocumentTenCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentTenCentAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentFiveCentAmount() != null && detail.getFinancialDocumentFiveCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentFiveCentAmount() == null || drawer.getFinancialDocumentFiveCentAmount().isLessThan(detail.getFinancialDocumentFiveCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"five cent amount", formatter.format(detail.getFinancialDocumentFiveCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentFiveCentAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentOneCentAmount() != null && detail.getFinancialDocumentOneCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentOneCentAmount() == null || drawer.getFinancialDocumentOneCentAmount().isLessThan(detail.getFinancialDocumentOneCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"one cent amount", formatter.format(detail.getFinancialDocumentOneCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentOneCentAmount()).toString()});
                    success = false;
                }
            }
            if (detail.getFinancialDocumentOtherCentAmount() != null && detail.getFinancialDocumentOtherCentAmount().isGreaterThan(KualiDecimal.ZERO)) {
                if (drawer.getFinancialDocumentOtherCentAmount() == null || drawer.getFinancialDocumentOtherCentAmount().isLessThan(detail.getFinancialDocumentOtherCentAmount())) {
                    GlobalVariables.getMessageMap().putError(KFSConstants.DepositConstants.DEPOSIT_WIZARD_DEPOSITHEADER_ERROR, KFSKeyConstants.Deposit.ERROR_NOT_ENOUGH_CASH_TO_COMPLETE_DEPOSIT, new String[]{"other cent amount", formatter.format(detail.getFinancialDocumentOtherCentAmount()).toString(), formatter.format(drawer.getFinancialDocumentOtherCentAmount()).toString()});
                    success = false;
                }
            }
        }
        return success;
    }


    /**
     * This method handles canceling (closing) the deposit wizard.
     *
     * @param mapping
     * @param form
     * @param request
     * @param response
     * @return ActionForward
     * @throws Exception
     */
    public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
        DepositWizardForm dform = (DepositWizardForm) form;

        ActionForward dest = returnToSender(dform.getCashManagementDocId());
        return dest;
    }


    /**
     * @param cmDocId
     * @return ActionForward which will redirect the user to the docSearchDisplay for the CashManagementDocument with the given
     * documentId
     */
    private ActionForward returnToSender(String cmDocId) {
        Properties params = new Properties();
        params.setProperty("methodToCall", "docHandler");
        params.setProperty("command", "displayDocSearchView");
        params.setProperty("docId", cmDocId);

        String cmActionUrl = UrlFactory.parameterizeUrl(KFSConstants.CASH_MANAGEMENT_DOCUMENT_ACTION, params);

        return new ActionForward(cmActionUrl, true);
    }
}

