/*
* 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.tem.document.authorization;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.module.tem.TemConstants;
import org.kuali.kfs.module.tem.TemConstants.TravelAuthorizationStatusCodeKeys;
import org.kuali.kfs.module.tem.TemConstants.TravelDocTypes;
import org.kuali.kfs.module.tem.TemPropertyConstants;
import org.kuali.kfs.module.tem.TemWorkflowConstants;
import org.kuali.kfs.module.tem.document.TravelAuthorizationDocument;
import org.kuali.kfs.module.tem.document.TravelReimbursementDocument;
import org.kuali.kfs.module.tem.document.service.TravelDocumentService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* Travel Reimbursement Document Presentation Controller
*
*/
public class TravelAuthorizationDocumentPresentationController extends TravelAuthorizationFamilyDocumentPresentationController {
/**
* @see org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationControllerBase#getEditModes(org.kuali.rice.kns.document.Document)
*/
@Override
public Set<String> getEditModes(Document document) {
WorkflowDocument wfDocument = document.getDocumentHeader().getWorkflowDocument();
Set<String> editModes = super.getEditModes(document);
addFullEntryEditMode(document, editModes);
editModes.remove(TemConstants.EditModes.CHECK_AMOUNT_ENTRY);
editModes.add(TemConstants.TravelEditMode.ADVANCE_PAYMENT_ENTRY);
if (shouldAllowBlanketTravelEntry(document)) {
editModes.add(TemConstants.EditModes.BLANKET_TRAVEL_ENTRY);
}
if (!wfDocument.isInitiated() && !wfDocument.isSaved()) {
editModes.add(TemConstants.EditModes.BLANKET_TRAVEL_VIEW);
}
if (document instanceof TravelAuthorizationDocument && ((TravelAuthorizationDocument)document).shouldProcessAdvanceForDocument() && isAtTravelerNode(wfDocument) || wfDocument.isInitiated() || wfDocument.isSaved()) {
editModes.add(TemConstants.TravelEditMode.ADVANCE_POLICY_ENTRY);
}
if (document instanceof TravelAuthorizationDocument && ((TravelAuthorizationDocument)document).shouldProcessAdvanceForDocument() && isAtTravelNode(wfDocument)) {
editModes.add(TemConstants.TravelEditMode.CLEAR_ADVANCE_MODE);
}
final Set<String> nodeNames = document.getDocumentHeader().getWorkflowDocument().getNodeNames();
if (wfDocument.isInitiated() || wfDocument.isSaved() || (nodeNames != null && !nodeNames.isEmpty() && (nodeNames.contains(TemWorkflowConstants.RouteNodeNames.TAX) || nodeNames.contains(TemWorkflowConstants.RouteNodeNames.AP_TRAVEL)))) {
editModes.add(TemConstants.EditModes.EXPENSE_TAXABLE_MODE);
}
if (wfDocument.isInitiated() || wfDocument.isSaved() || (nodeNames != null && !nodeNames.isEmpty() && nodeNames.contains(TemWorkflowConstants.RouteNodeNames.AP_TRAVEL))) {
editModes.add(TemConstants.EditModes.EXPENSE_LIMIT_ENTRY);
}
return editModes;
}
/**
* Determines whether blanket travel selection will be given at all on the travel authorization document. If a trip type is not
* selected, or the trip type does not allow blanket travel, then the blanket travel indicator will not be shown; otherwise, if
* there is a trip type and it allows blanket travel, the selection option will be shown
* @return true if the blanket travel selection should be shown, false otherwise
*/
protected boolean shouldAllowBlanketTravelEntry(Document document) {
if (!(document instanceof TravelAuthorizationDocument)) {
return false; // also, you're using the wrong damn authorizer
}
final TravelAuthorizationDocument travelAuthorization = (TravelAuthorizationDocument)document;
if ((!travelAuthorization.getDocumentHeader().getWorkflowDocument().isInitiated() && !travelAuthorization.getDocumentHeader().getWorkflowDocument().isSaved()) || StringUtils.isBlank(travelAuthorization.getTripTypeCode())) {
return false;
}
if (ObjectUtils.isNull(travelAuthorization.getTripType())) {
travelAuthorization.refreshReferenceObject(TemPropertyConstants.TRIP_TYPE);
}
return !ObjectUtils.isNull(travelAuthorization.getTripType()) && travelAuthorization.getTripType().isBlanketTravel();
}
/**
* Determines if the current workflow document is at the Travel node
* @param workflowDocument the workflow document to check the node of
* @return true if the document is at the Travel node, false otherwise
*/
public boolean isAtTravelNode(WorkflowDocument workflowDocument) {
return workflowDocument.getCurrentNodeNames().contains(TemWorkflowConstants.RouteNodeNames.AP_TRAVEL);
}
/**
* Determines if the current workflow document is at the Traveler node
* @param workflowDocument the workflow document to check the node of
* @return true if the document is at the Traveler node, false otherwise
*/
public boolean isAtTravelerNode(WorkflowDocument workflowDocument) {
return workflowDocument.getCurrentNodeNames().contains(TemWorkflowConstants.RouteNodeNames.TRAVELER);
}
/**
* Overridden to handle travel authorization specific actions, such as amend, hold, remove hold, close TA, cancel TA, and pay vendor
* @see org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationControllerBase#getDocumentActions(org.kuali.rice.krad.document.Document)
*/
@Override
public Set<String> getDocumentActions(Document document) {
Set<String> actions = super.getDocumentActions(document);
final TravelAuthorizationDocument travelAuth = (TravelAuthorizationDocument)document;
if (canAmend(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_AMEND);
}
if (canHold(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_HOLD);
}
if (canRemoveHold(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_REMOVE_HOLD);
}
if (canCloseAuthorization(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_CLOSE_TA);
}
if (canCancelAuthorization(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_CANCEL_TA);
}
if (canNewReimbursement(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_NEW_REIMBURSEMENT);
}
if (canPayVendor(travelAuth)) {
actions.add(TemConstants.TravelAuthorizationActions.CAN_PAY_VENDOR);
}
return actions;
}
/**
* Determines whether the authorization is in a state where it can be amended
* @param document the authorization to test
* @return true if the authorization can be amended, false otherwise
*/
public boolean canAmend(TravelAuthorizationDocument document) {
boolean can = isOpen(document) && (isFinalOrProcessed(document));
if (can) {
//If there are TR's that are not canceled or disapproved, disabled amend
final TravelDocumentService travelDocumentService = SpringContext.getBean(TravelDocumentService.class);
final List<Document> trRelatedDocumentList = travelDocumentService.getDocumentsRelatedTo(document, TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
for (Document trDocument : trRelatedDocumentList) {
can &= trDocument.getDocumentHeader().getWorkflowDocument().isCanceled() || trDocument.getDocumentHeader().getWorkflowDocument().isDisapproved();
}
}
return can;
}
/**
* Determines if the authorization can be held
* @param document the authorization to check
* @return true if the authorization can be held, false otherwise
*/
public boolean canHold(TravelAuthorizationDocument document) {
return isOpen(document) && (isFinalOrProcessed(document));
}
/**
* Determines if the authorization can remove a hold on it
* @param document the authorization to check
* @return true if the authorization is held and that hold can be removed, false otherwise
*/
public boolean canRemoveHold(TravelAuthorizationDocument document) {
return isHeld(document) && (isFinalOrProcessed(document));
}
/**
* Determines if the authorization can be closed
* @param document the authorization to check
* @return true if the authorization can be closed, false otherwise
*/
public boolean canCloseAuthorization(TravelAuthorizationDocument document) {
return isOpen(document) && (isFinalOrProcessed(document)) && hasReimbursements(document) && !hasEnrouteReimbursements(document);
}
/**
* Determines if the authorization can be canceled. Default logic verifies that the authorization is open and has not yet been reimbursed
* @param document the authorization to check
* @return true if the authorization can be canceled, false otherwise
*/
public boolean canCancelAuthorization(TravelAuthorizationDocument document) {
boolean can = isOpen(document) && (isFinalOrProcessed(document));
// verify that there are no reimbursements out there for this doc
if (can) {
if (hasReimbursements(document)) {
can = false;
}
}
return can;
}
/**
* Determines if a reimbursement can be initiated for this document. This is done for {@link TravelAuthorizationDocument} instances
* that have a workflow document status of FINAL or PROCESSED and on documents that do not have a workflow
* App Doc Status of REIMB_HELD, CANCELLED, PEND_AMENDMENT, CLOSED, or RETIRED_VERSION. Also checks if the person has permission to
* initiate the target documents. Will not show the new Reimbursement link if a TA already has a TR enroute.
*
* If the document is a TAC, the workflow document status must be FINAL and the App Doc Status must be CLOSED.
*
* check status of document and don't create if the status is not final or processed
*
* @param document
* @return
*/
public boolean canNewReimbursement(TravelAuthorizationDocument document) {
final String documentType = document.getDocumentTypeName();
boolean documentStatusCheck = isFinalOrProcessed(document);
final String appDocStatus = document.getApplicationDocumentStatus();
boolean appDocStatusCheck = (!appDocStatus.equals(TravelAuthorizationStatusCodeKeys.REIMB_HELD)
&& !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.CANCELLED)
&& !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.PEND_AMENDMENT)
&& !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.RETIRED_VERSION)
&& !appDocStatus.equals(TravelAuthorizationStatusCodeKeys.CLOSED));
boolean statusCheck = documentStatusCheck && appDocStatusCheck;
Person user = GlobalVariables.getUserSession().getPerson();
boolean hasInitAccess = false;
if (getTemRoleService().canAccessTravelDocument(document, user) && !ObjectUtils.isNull(document.getTraveler()) && document.getTemProfileId() != null && !ObjectUtils.isNull(document.getTemProfile())){
//check if user also can init other docs
hasInitAccess = user.getPrincipalId().equals(document.getTraveler().getPrincipalId()) || getTemRoleService().isTravelDocumentArrangerForProfile(documentType, user.getPrincipalId(), document.getTemProfileId()) || getTemRoleService().isTravelArranger(user, document.getTemProfile().getHomeDepartment() , document.getTemProfileId().toString(), documentType);
}
boolean checkRelatedDocs = true;
//check whether there is already an ENROUTE TR
List<Document> docs = getTravelDocumentService().getDocumentsRelatedTo(document, TemConstants.TravelDocTypes.TRAVEL_REIMBURSEMENT_DOCUMENT);
for (Document doc : docs) {
TravelReimbursementDocument trDoc = (TravelReimbursementDocument)doc;
if (trDoc.getDocumentHeader().getWorkflowDocument().isEnroute()) {
checkRelatedDocs &= false;
}
}
//a TR document can be processed against a closed TA. If the TAC is Final/Closed display the TR link.
if (StringUtils.equalsIgnoreCase(documentType, TemConstants.TravelDocTypes.TRAVEL_AUTHORIZATION_CLOSE_DOCUMENT)) {
documentStatusCheck = document.getDocumentHeader().getWorkflowDocument().isFinal();
appDocStatusCheck = appDocStatus.equals(TravelAuthorizationStatusCodeKeys.CLOSED);
statusCheck = documentStatusCheck && appDocStatusCheck;
}
return statusCheck && hasInitAccess && checkRelatedDocs;
}
/**
* is this document on hold for reimbursement workflow state?
*
* @param document the travel authorization to check
* @return true if the document is in held status, false otherwise
*/
protected boolean isHeld(TravelAuthorizationDocument document) {
return TemConstants.TravelAuthorizationStatusCodeKeys.REIMB_HELD.equals(document.getAppDocStatus());
}
/**
* Determines if the given TravelAuthorizationDocument has reimbursements or not
* @param document the authorization to check
* @return true if the authorization has reimbursements against it, false otherwise
*/
protected boolean hasReimbursements(TravelAuthorizationDocument document) {
List<TravelReimbursementDocument> reimbursements = getTravelDocumentService().findReimbursementDocuments(document.getTravelDocumentIdentifier());
return reimbursements != null && !reimbursements.isEmpty();
}
/**
* Determines if the given travel authorization document has any enroute reimbursements
* @param document the travel authorization to find enroute reimbursements for
* @return true if there are any enroute reimbursements, false otherwise
*/
protected boolean hasEnrouteReimbursements(TravelAuthorizationDocument document) {
List<TravelReimbursementDocument> reimbursements = getTravelDocumentService().findReimbursementDocuments(document.getTravelDocumentIdentifier());
for (TravelReimbursementDocument reimbursement : reimbursements) {
if (StringUtils.equals(KFSConstants.DocumentStatusCodes.ENROUTE, reimbursement.getFinancialSystemDocumentHeader().getFinancialDocumentStatusCode())) {
return true;
}
}
return false;
}
}