/*
* 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.sys.document;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.dataaccess.FinancialSystemDocumentHeaderDao;
import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.WorkflowRuntimeException;
import org.kuali.rice.kew.api.document.DocumentStatus;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteLevelChange;
import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
import org.kuali.rice.kns.service.DocumentHelperService;
import org.kuali.rice.krad.bo.DocumentHeader;
import org.kuali.rice.krad.document.TransactionalDocumentBase;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* This class is a KFS specific TransactionalDocumentBase class
*/
public class FinancialSystemTransactionalDocumentBase extends TransactionalDocumentBase implements FinancialSystemTransactionalDocument {
private static final Logger LOG = Logger.getLogger(FinancialSystemTransactionalDocumentBase.class);
protected static final String UPDATE_TOTAL_AMOUNT_IN_POST_PROCESSING_PARAMETER_NAME = "UPDATE_TOTAL_AMOUNT_IN_POST_PROCESSING_IND";
private static transient BusinessObjectService businessObjectService;
private static transient FinancialSystemDocumentService financialSystemDocumentService;
private static transient ParameterService parameterService;
private transient Map<String,Boolean> canEditCache;
/**
* Constructs a FinancialSystemTransactionalDocumentBase.java.
*/
public FinancialSystemTransactionalDocumentBase() {
super();
}
@Override
public FinancialSystemDocumentHeader getFinancialSystemDocumentHeader() {
return (FinancialSystemDocumentHeader) documentHeader;
}
/**
* @see org.kuali.rice.krad.document.DocumentBase#setDocumentHeader(org.kuali.rice.krad.bo.DocumentHeader)
*/
@Override
public void setDocumentHeader(DocumentHeader documentHeader) {
if ((documentHeader != null) && (!FinancialSystemDocumentHeader.class.isAssignableFrom(documentHeader.getClass()))) {
throw new IllegalArgumentException("document header of class '" + documentHeader.getClass() + "' is not assignable from financial document header class '" + FinancialSystemDocumentHeader.class + "'");
}
this.documentHeader = documentHeader;
}
/**
* If the document has a total amount, call method on document to get the total and set in doc header.
*
* @see org.kuali.rice.krad.document.Document#prepareForSave()
*/
@Override
public void prepareForSave() {
if (this instanceof AmountTotaling) {
getFinancialSystemDocumentHeader().setFinancialDocumentTotalAmount(((AmountTotaling) this).getTotalDollarAmount());
}
captureWorkflowHeaderInformation();
super.prepareForSave();
}
/**
* Attempts to capture the document type name, initiator principal id, and KEW document status code to the Financial System Document Header
*/
protected void captureWorkflowHeaderInformation() {
if (StringUtils.isBlank(getFinancialSystemDocumentHeader().getInitiatorPrincipalId())) {
getFinancialSystemDocumentHeader().setInitiatorPrincipalId(getFinancialSystemDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId());
}
if (StringUtils.isBlank(getFinancialSystemDocumentHeader().getWorkflowDocumentTypeName())) {
getFinancialSystemDocumentHeader().setWorkflowDocumentTypeName(getFinancialSystemDocumentHeader().getWorkflowDocument().getDocumentTypeName());
}
if (ObjectUtils.isNull(getFinancialSystemDocumentHeader().getWorkflowCreateDate())) {
getFinancialSystemDocumentHeader().setWorkflowCreateDate(new java.sql.Timestamp(getFinancialSystemDocumentHeader().getWorkflowDocument().getDateCreated().getMillis()));
}
final String statusCode = getWorkflowDocumentStatusCode(getFinancialSystemDocumentHeader().getWorkflowDocument().getStatus());
getFinancialSystemDocumentHeader().setWorkflowDocumentStatusCode(statusCode);
}
/**
* Given a DocumentStatus, returns the code for that status. Allows us a shim to change initiated's into saved's
* @param status the status to return a code for
* @return the code for that stat
*/
protected String getWorkflowDocumentStatusCode(DocumentStatus status) {
// we're preparing to save here. If the save fails, the transaction should roll back - so the fact that the doc header is in saved mode shouldn't
// cause problems. And since org.kuali.rice.krad.service.impl.PostProcessorServiceImpl#doRouteStatusChange will NOT save the document when the
// DocStatus is saved, let's simply pre-anticipate that
final String statusCode = status.equals(DocumentStatus.INITIATED) ?
DocumentStatus.SAVED.getCode() :
getFinancialSystemDocumentHeader().getWorkflowDocument().getStatus().getCode();
return statusCode;
}
/**
* This is the default implementation which ensures that document note attachment references are loaded.
*
* @see org.kuali.rice.krad.document.Document#processAfterRetrieve()
*/
@Override
public void processAfterRetrieve() {
// set correctedByDocumentId manually, since OJB doesn't maintain that relationship
try {
DocumentHeader correctingDocumentHeader = SpringContext.getBean(FinancialSystemDocumentHeaderDao.class).getCorrectingDocumentHeader(getFinancialSystemDocumentHeader().getDocumentNumber());
if (ObjectUtils.isNotNull(correctingDocumentHeader) && !correctingDocumentHeader.getWorkflowDocument().isCanceled() && !correctingDocumentHeader.getWorkflowDocument().isDisapproved()) {
getFinancialSystemDocumentHeader().setCorrectedByDocumentId(correctingDocumentHeader.getDocumentNumber());
}
} catch (Exception e) {
LOG.error("Received WorkflowException trying to get route header id from workflow document.", e);
throw new WorkflowRuntimeException(e);
}
// set the ad hoc route recipients too, since OJB doesn't maintain that relationship
// TODO - see KULNRVSYS-1054
super.processAfterRetrieve();
}
/**
* This is the default implementation which checks for a different workflow statuses, and updates the Kuali status accordingly.
*
* @see org.kuali.rice.krad.document.Document#doRouteStatusChange()
*/
@Override
public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) {
// set the route status
getFinancialSystemDocumentHeader().setWorkflowDocumentStatusCode(getWorkflowDocumentStatusCode(DocumentStatus.fromCode(statusChangeEvent.getNewRouteStatus())));
getFinancialSystemDocumentHeader().setApplicationDocumentStatus(getFinancialSystemDocumentHeader().getWorkflowDocument().getApplicationDocumentStatus());
if (getDocumentHeader().getWorkflowDocument().isCanceled()) {
getFinancialSystemDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.CANCELLED);
}
else if (getDocumentHeader().getWorkflowDocument().isEnroute()) {
getFinancialSystemDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.ENROUTE);
}
if (getDocumentHeader().getWorkflowDocument().isDisapproved()) {
getFinancialSystemDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.DISAPPROVED);
}
if (getDocumentHeader().getWorkflowDocument().isProcessed()) {
getFinancialSystemDocumentHeader().setFinancialDocumentStatusCode(KFSConstants.DocumentStatusCodes.APPROVED);
}
if ( LOG.isInfoEnabled() ) {
LOG.info("Document: " + statusChangeEvent.getDocumentId() + " -- Status is: " + getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode());
}
super.doRouteStatusChange(statusChangeEvent);
}
/**
* This is the default implementation which, if parameter KFS-SYS / Document / UPDATE_TOTAL_AMOUNT_IN_POST_PROCESSING_IND is on, updates the document
* and resaves if needed
* @see org.kuali.rice.kns.document.DocumentBase#doRouteLevelChange(org.kuali.rice.kew.dto.DocumentRouteLevelChangeDTO)
*/
@Override
public void doRouteLevelChange(DocumentRouteLevelChange levelChangeEvent) {
// grab the new app doc status
getFinancialSystemDocumentHeader().setApplicationDocumentStatus(getFinancialSystemDocumentHeader().getWorkflowDocument().getApplicationDocumentStatus());
if (this instanceof AmountTotaling
&& getDocumentHeader() != null
&& getParameterService() != null
&& getBusinessObjectService() != null
&& getParameterService().parameterExists(KfsParameterConstants.FINANCIAL_SYSTEM_DOCUMENT.class, UPDATE_TOTAL_AMOUNT_IN_POST_PROCESSING_PARAMETER_NAME)
&& getParameterService().getParameterValueAsBoolean(KfsParameterConstants.FINANCIAL_SYSTEM_DOCUMENT.class, UPDATE_TOTAL_AMOUNT_IN_POST_PROCESSING_PARAMETER_NAME)) {
final KualiDecimal currentTotal = ((AmountTotaling)this).getTotalDollarAmount();
if (!currentTotal.equals(getFinancialSystemDocumentHeader().getFinancialDocumentTotalAmount())) {
getFinancialSystemDocumentHeader().setFinancialDocumentTotalAmount(currentTotal);
}
}
if (this instanceof AmountTotaling || !StringUtils.isBlank(getFinancialSystemDocumentHeader().getApplicationDocumentStatus())) {
getBusinessObjectService().save(getFinancialSystemDocumentHeader());
}
super.doRouteLevelChange(levelChangeEvent);
}
/**
* @see org.kuali.kfs.sys.document.Correctable#toErrorCorrection()
*/
public void toErrorCorrection() throws WorkflowException, IllegalStateException {
DocumentHelperService documentHelperService = SpringContext.getBean(DocumentHelperService.class);
final Set<String> documentActionsFromPresentationController = documentHelperService.getDocumentPresentationController(this).getDocumentActions(this);
final Set<String> documentActionsFromAuthorizer = documentHelperService.getDocumentAuthorizer(this).getDocumentActions(this, GlobalVariables.getUserSession().getPerson(), documentActionsFromPresentationController);
if (!documentActionsFromAuthorizer.contains(KFSConstants.KFS_ACTION_CAN_ERROR_CORRECT)) {
throw new IllegalStateException(this.getClass().getName() + " does not support document-level error correction");
}
String sourceDocumentHeaderId = getDocumentNumber();
setNewDocumentHeader();
getFinancialSystemDocumentHeader().setFinancialDocumentInErrorNumber(sourceDocumentHeaderId);
//clear out notes from previous bo
getNotes().clear();
addCopyErrorDocumentNote("error-correction for document " + sourceDocumentHeaderId);
}
@Override
public boolean answerSplitNodeQuestion(String nodeName) throws UnsupportedOperationException {
throw new UnsupportedOperationException("No split node logic defined for split node "+nodeName+" on " + this.getClass().getSimpleName());
}
/**
* @return the default implementation of the ParameterService
*/
protected ParameterService getParameterService() {
if (parameterService == null) {
parameterService = SpringContext.getBean(ParameterService.class);
}
return parameterService;
}
/**
* @return the default implementation of the BusinessObjectService
*/
protected BusinessObjectService getBusinessObjectService() {
if (businessObjectService == null) {
businessObjectService = SpringContext.getBean(BusinessObjectService.class);
}
return businessObjectService;
}
protected FinancialSystemDocumentService getFinancialSystemDocumentService() {
if (financialSystemDocumentService == null) {
financialSystemDocumentService = SpringContext.getBean(FinancialSystemDocumentService.class);
}
return financialSystemDocumentService;
}
@Override
public void toCopy() throws WorkflowException, IllegalStateException {
FinancialSystemDocumentHeader oldDocumentHeader = getFinancialSystemDocumentHeader();
super.toCopy();
getFinancialSystemDocumentService().prepareToCopy(oldDocumentHeader, this);
}
/**
* Updates status of this document and saves the workflow data
*
* @param applicationDocumentStatus is the app doc status to save
* @throws WorkflowException
*/
@Override
public void updateAndSaveAppDocStatus(String applicationDocumentStatus) throws WorkflowException {
getFinancialSystemDocumentHeader().updateAndSaveAppDocStatus(applicationDocumentStatus);
}
/**
* Gets the applicationDocumentStatus attribute.
*
* @return Returns the applicationDocumentStatus
*/
@Override
public String getApplicationDocumentStatus() {
return getFinancialSystemDocumentHeader().getApplicationDocumentStatus();
}
/**
* Sets the applicationDocumentStatus attribute.
*
* @param applicationDocumentStatus The applicationDocumentStatus to set.
*/
@Override
public void setApplicationDocumentStatus(String applicationDocumentStatus) {
getFinancialSystemDocumentHeader().setApplicationDocumentStatus(applicationDocumentStatus);
}
}