/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.ar.document.web.struts;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.module.ar.ArConstants;
import org.kuali.kfs.module.ar.ArKeyConstants;
import org.kuali.kfs.module.ar.ArPropertyConstants;
import org.kuali.kfs.module.ar.businessobject.AccountsReceivableDocumentHeader;
import org.kuali.kfs.module.ar.businessobject.CashControlDetail;
import org.kuali.kfs.module.ar.document.CashControlDocument;
import org.kuali.kfs.module.ar.document.PaymentApplicationDocument;
import org.kuali.kfs.module.ar.document.service.AccountsReceivableDocumentHeaderService;
import org.kuali.kfs.module.ar.document.service.CashControlDocumentService;
import org.kuali.kfs.module.ar.document.validation.event.AddCashControlDetailEvent;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.Bank;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.web.struts.FinancialSystemTransactionalDocumentActionBase;
import org.kuali.kfs.sys.service.BankService;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.krad.exception.UnknownDocumentIdException;
import org.kuali.rice.krad.rules.rule.event.SaveDocumentEvent;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.SessionDocumentService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.krad.service.KualiRuleService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase;
import org.kuali.rice.kew.api.WorkflowDocument;
public class CashControlDocumentAction extends FinancialSystemTransactionalDocumentActionBase {
/**
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#loadDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
*/
@Override
protected void loadDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
super.loadDocument(kualiDocumentFormBase);
CashControlDocumentForm ccForm = (CashControlDocumentForm) kualiDocumentFormBase;
CashControlDocument cashControlDocument = ccForm.getCashControlDocument();
// now that the form has been originally loaded, we need to set a few Form variables that are used by
// JSP JSTL expressions because they are used directly and immediately upon initial form display
if (cashControlDocument != null && cashControlDocument.getCustomerPaymentMediumCode() != null) {
ccForm.setCashPaymentMediumSelected(ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(cashControlDocument.getCustomerPaymentMediumCode()));
}
if ( cashControlDocument != null ) {
// get the PaymentApplicationDocuments by reference number
for (CashControlDetail cashControlDetail : cashControlDocument.getCashControlDetails()) {
String docId = cashControlDetail.getReferenceFinancialDocumentNumber();
PaymentApplicationDocument doc = (PaymentApplicationDocument) SpringContext.getBean(DocumentService.class).getByDocumentHeaderId(docId);
if (doc == null) {
throw new UnknownDocumentIdException("Document " + docId + " no longer exists. It may have been cancelled before being saved.");
}
cashControlDetail.setReferenceFinancialDocument(doc);
WorkflowDocument workflowDoc = doc.getDocumentHeader().getWorkflowDocument();
// KualiDocumentFormBase.populate() needs this updated in the session
SpringContext.getBean(SessionDocumentService.class).addDocumentToUserSession(GlobalVariables.getUserSession(), workflowDoc);
}
}
}
/**
* Adds handling for cash control detail amount updates.
*
* @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 {
CashControlDocumentForm ccForm = (CashControlDocumentForm) form;
CashControlDocument ccDoc = ccForm.getCashControlDocument();
if (ccDoc != null) {
ccForm.setCashPaymentMediumSelected(ArConstants.PaymentMediumCode.CASH.equalsIgnoreCase(ccDoc.getCustomerPaymentMediumCode()));
}
if (ccForm.hasDocumentId()) {
ccDoc = ccForm.getCashControlDocument();
ccDoc.refreshReferenceObject("customerPaymentMedium");
// recalc b/c changes to the amounts could have happened
ccDoc.recalculateTotals();
}
return super.execute(mapping, form, request, response);
}
/**
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#createDocument(org.kuali.rice.kns.web.struts.form.KualiDocumentFormBase)
*/
@Override
protected void createDocument(KualiDocumentFormBase kualiDocumentFormBase) throws WorkflowException {
super.createDocument(kualiDocumentFormBase);
CashControlDocumentForm form = (CashControlDocumentForm) kualiDocumentFormBase;
CashControlDocument document = form.getCashControlDocument();
//get the default bank code for the given document type, which is CTRL for this document.
document.setBankCode("");
Bank defaultBank = SpringContext.getBean(BankService.class).getDefaultBankByDocType(form.getDocTypeName());
if (defaultBank != null) {
document.setBankCode(defaultBank.getBankCode());
}
// set up the default values for the AR DOC Header (SHOULD PROBABLY MAKE THIS A SERVICE)
AccountsReceivableDocumentHeaderService accountsReceivableDocumentHeaderService = SpringContext.getBean(AccountsReceivableDocumentHeaderService.class);
AccountsReceivableDocumentHeader accountsReceivableDocumentHeader = accountsReceivableDocumentHeaderService.getNewAccountsReceivableDocumentHeaderForCurrentUser();
accountsReceivableDocumentHeader.setDocumentNumber(document.getDocumentNumber());
document.setAccountsReceivableDocumentHeader(accountsReceivableDocumentHeader);
}
/**
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#cancel(org.apache.struts.action.ActionMapping,
* org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward cancel(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
// If the cancel works, proceed to canceling the cash control doc
if (cancelLinkedPaymentApplicationDocuments(cashControlDocument)) {
return super.cancel(mapping, form, request, response);
}
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* @see org.kuali.rice.kns.web.struts.action.KualiDocumentActionBase#disapprove(org.apache.struts.action.ActionMapping,
* org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
*/
@Override
public ActionForward disapprove(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
boolean success = true;
CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
success = cancelLinkedPaymentApplicationDocuments(cashControlDocument);
if (!success) {
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
return super.disapprove(mapping, form, request, response);
}
/**
* This method cancels all linked Payment Application documents that are not already in approved status.
*
* @param cashControlDocument
* @throws WorkflowException
*/
protected boolean cancelLinkedPaymentApplicationDocuments(CashControlDocument cashControlDocument) throws WorkflowException {
boolean success = true;
List<CashControlDetail> details = cashControlDocument.getCashControlDetails();
for (CashControlDetail cashControlDetail : details) {
DocumentService documentService = SpringContext.getBean(DocumentService.class);
PaymentApplicationDocument applicationDocument = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(cashControlDetail.getReferenceFinancialDocumentNumber());
if (KFSConstants.DocumentStatusCodes.CANCELLED.equals(applicationDocument.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
// Ignore this case, as it should not impact the ability to cancel a cash control doc.
}
else if (!KFSConstants.DocumentStatusCodes.APPROVED.equals(applicationDocument.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
documentService.cancelDocument(applicationDocument, ArKeyConstants.DOCUMENT_DELETED_FROM_CASH_CTRL_DOC);
}
else {
GlobalVariables.getMessageMap().putError(ArPropertyConstants.CashControlDetailFields.CASH_CONTROL_DETAILS_TAB, ArKeyConstants.ERROR_CANT_CANCEL_CASH_CONTROL_DOC_WITH_ASSOCIATED_APPROVED_PAYMENT_APPLICATION);
success = false;
}
}
return success;
}
/**
* This method adds a new cash control detail
*
* @param mapping action mapping
* @param form action form
* @param request
* @param response
* @return forward action
* @throws Exception
*/
public ActionForward addCashControlDetail(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
ConfigurationService kualiConfiguration = SpringContext.getBean(ConfigurationService.class);
CashControlDetail newCashControlDetail = cashControlDocForm.getNewCashControlDetail();
newCashControlDetail.setDocumentNumber(cashControlDocument.getDocumentNumber());
String customerNumber = newCashControlDetail.getCustomerNumber();
if (StringUtils.isNotEmpty(customerNumber)) {
// force customer numbers to upper case, since its a primary key
customerNumber = customerNumber.toUpperCase();
}
newCashControlDetail.setCustomerNumber(customerNumber);
// save the document, which will run business rules and make sure the doc is ready for lines
KualiRuleService ruleService = SpringContext.getBean(KualiRuleService.class);
boolean rulePassed = true;
// apply save rules for the doc
rulePassed &= ruleService.applyRules(new SaveDocumentEvent(KFSConstants.DOCUMENT_HEADER_ERRORS, cashControlDocument));
// apply rules for the new cash control detail
rulePassed &= ruleService.applyRules(new AddCashControlDetailEvent(ArConstants.NEW_CASH_CONTROL_DETAIL_ERROR_PATH_PREFIX, cashControlDocument, newCashControlDetail));
// add the new detail if rules passed
if (rulePassed) {
CashControlDocumentService cashControlDocumentService = SpringContext.getBean(CashControlDocumentService.class);
// add cash control detail. implicitly saves the cash control document
cashControlDocumentService.addNewCashControlDetail(kualiConfiguration.getPropertyValueAsString(ArKeyConstants.CREATED_BY_CASH_CTRL_DOC), cashControlDocument, newCashControlDetail);
// set a new blank cash control detail
cashControlDocForm.setNewCashControlDetail(new CashControlDetail());
}
// recalc totals, including the docHeader total
cashControlDocument.recalculateTotals();
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This method deletes a cash control detail
*
* @param mapping action mapping
* @param form action form
* @param request
* @param response
* @return action forward
* @throws Exception
*/
public ActionForward deleteCashControlDetail(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
int indexOfLineToDelete = getLineToDelete(request);
CashControlDetail cashControlDetail = cashControlDocument.getCashControlDetail(indexOfLineToDelete);
DocumentService documentService = SpringContext.getBean(DocumentService.class);
PaymentApplicationDocument payAppDoc = (PaymentApplicationDocument) documentService.getByDocumentHeaderId(cashControlDetail.getReferenceFinancialDocumentNumber());
// this if statement is to catch the situation where a person deletes the line, but doesnt save
// and then reloads. This will bring the deleted line back on the screen. If they then click
// the delete line, and this test isnt here, it will try to cancel the already cancelled
// document, which will throw a workflow error and barf.
if (!payAppDoc.getDocumentHeader().getWorkflowDocument().isCanceled()) {
documentService.cancelDocument(payAppDoc, ArKeyConstants.DOCUMENT_DELETED_FROM_CASH_CTRL_DOC);
}
cashControlDocument.deleteCashControlDetail(indexOfLineToDelete);
// recalc totals, including the docHeader total
cashControlDocument.recalculateTotals();
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* This method generates the GLPEs.
*
* @param mapping action mapping
* @param form action form
* @param request
* @param response
* @return action forward
* @throws Exception
*/
public ActionForward generateGLPEs(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
CashControlDocumentForm cashControlDocForm = (CashControlDocumentForm) form;
CashControlDocument cashControlDocument = cashControlDocForm.getCashControlDocument();
String paymentMediumCode = cashControlDocument.getCustomerPaymentMediumCode();
// refresh reference objects
cashControlDocument.refreshReferenceObject("customerPaymentMedium");
cashControlDocument.refreshReferenceObject("generalLedgerPendingEntries");
// payment medium might have been changed meanwhile so we save first the document
BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
businessObjectService.save(cashControlDocument);
// generate the GLPEs
GeneralLedgerPendingEntryService glpeService = SpringContext.getBean(GeneralLedgerPendingEntryService.class);
boolean success = glpeService.generateGeneralLedgerPendingEntries(cashControlDocument);
if (!success) {
GlobalVariables.getMessageMap().putError(KFSConstants.GENERAL_LEDGER_PENDING_ENTRIES_TAB_ERRORS, ArKeyConstants.ERROR_GLPES_NOT_CREATED);
}
// approve the GLPEs
cashControlDocument.changeGeneralLedgerPendingEntriesApprovedStatusCode();
// save the GLPEs in the database
CashControlDocumentService cashControlDocumentService = SpringContext.getBean(CashControlDocumentService.class);
cashControlDocumentService.saveGLPEs(cashControlDocument);
// approve the document when the GLPEs are generated
// DocumentService docService = SpringContext.getBean(DocumentService.class);
// docService.approveDocument(cashControlDocument, "Automatically approved document with GLPE generation.", null);
return mapping.findForward(KFSConstants.MAPPING_BASIC);
}
/**
* Recalculates the cash control total since user could have changed it during their update.
*
* @param cashControlDocument
*/
protected KualiDecimal calculateCashControlTotal(CashControlDocument cashControlDocument) {
KualiDecimal total = KualiDecimal.ZERO;
for (CashControlDetail cashControlDetail : cashControlDocument.getCashControlDetails()) {
total = total.add(cashControlDetail.getFinancialDocumentLineAmount());
}
return total;
}
}