/*
* 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.purap.document.authorization;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
import org.kuali.kfs.module.purap.businessobject.PurApItem;
import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocumentBase;
import org.kuali.kfs.module.purap.document.RequisitionDocument;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.AccountingDocument;
import org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase;
import org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentAuthorizerBase;
import org.kuali.kfs.sys.document.authorization.FinancialSystemTransactionalDocumentPresentationController;
import org.kuali.kfs.sys.document.web.AccountingLineRenderingContext;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kns.datadictionary.TransactionalDocumentEntry;
import org.kuali.rice.kns.document.authorization.DocumentAuthorizer;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.krad.document.DocumentPresentationController;
/**
* Authorizer which deals with financial processing document issues, specifically sales tax lines on documents
* This class utilizes the new accountingLine model.
*/
public class PurapAccountingLineAuthorizer extends AccountingLineAuthorizerBase {
/**
* Overrides the method in AccountingLineAuthorizerBase so that the add button would
* have the line item number in addition to the rest of the insertxxxx String for
* methodToCall when the user clicks on the add button.
*
* @param accountingLine
* @param accountingLineProperty
* @return
*/
@Override
protected String getAddMethod(AccountingLine accountingLine, String accountingLineProperty) {
final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
String lineNumber = null;
if (accountingLineProperty.equals(PurapPropertyConstants.ACCOUNT_DISTRIBUTION_NEW_SRC_LINE)) {
lineNumber = "-2";
}
else {
lineNumber = StringUtils.substringBetween(accountingLineProperty, "[", "]");
}
return "insert"+infix + "Line.line" + lineNumber + "." + "anchoraccounting"+infix+"Anchor";
}
/**
* Overrides the method in AccountingLineAuthorizerBase so that the delete button would have both
* the line item number and the accounting line number for methodToCall when the user clicks on
* the delete button.
*
* @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getDeleteLineMethod(org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer)
*/
@Override
protected String getDeleteLineMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
final String infix = getActionInfixForExtantAccountingLine(accountingLine, accountingLineProperty);
String lineNumber = StringUtils.substringBetween(accountingLineProperty, "item[", "].sourceAccountingLine");
if (lineNumber == null) {
lineNumber = "-2";
}
String accountingLineNumber = StringUtils.substringBetween(accountingLineProperty, "sourceAccountingLine[", "]");
return "delete"+infix+"Line.line"+ lineNumber + ":" + accountingLineNumber + ".anchoraccounting"+infix+"Anchor";
}
/**
* Overrides the method in AccountingLineAuthorizerBase so that the balance inquiry button would
* have both the line item number and the accounting line number for methodToCall when the user
* clicks on the balance inquiry button.
* @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getBalanceInquiryMethod(org.kuali.kfs.sys.businessobject.AccountingLine, java.lang.String, java.lang.Integer)
*/
@Override
protected String getBalanceInquiryMethod(AccountingLine accountingLine, String accountingLineProperty, Integer accountingLineIndex) {
final String infix = getActionInfixForNewAccountingLine(accountingLine, accountingLineProperty);
String lineNumber = StringUtils.substringBetween(accountingLineProperty, "item[", "].sourceAccountingLine");
if (lineNumber == null) {
lineNumber = "-2";
}
String accountingLineNumber = StringUtils.substringBetween(accountingLineProperty, "sourceAccountingLine[", "]");
return "performBalanceInquiryFor"+infix+"Line.line"+ ":" + lineNumber + ":" + accountingLineNumber + ".anchoraccounting"+infix+ "existingLineLineAnchor"+accountingLineNumber;
}
/**
* @see org.kuali.kfs.sys.document.authorization.AccountingLineAuthorizerBase#getUnviewableBlocks(org.kuali.kfs.sys.document.AccountingDocument, org.kuali.kfs.sys.businessobject.AccountingLine, boolean, org.kuali.rice.kim.api.identity.Person)
*/
@Override
public Set<String> getUnviewableBlocks(AccountingDocument accountingDocument, AccountingLine accountingLine, boolean newLine, Person currentUser) {
Set<String> unviewableBlocks = super.getUnviewableBlocks(accountingDocument, accountingLine, newLine, currentUser);
if (showAmountOnly(accountingDocument)) {
unviewableBlocks.add(KFSPropertyConstants.PERCENT);
}
else {
unviewableBlocks.add(KFSPropertyConstants.AMOUNT);
}
return unviewableBlocks;
}
private boolean showAmountOnly(AccountingDocument accountingDocument) {
PurapService purapService = SpringContext.getBean(PurapService.class);
if (accountingDocument instanceof PurchasingAccountsPayableDocument) {
if (purapService.isFullDocumentEntryCompleted((PurchasingAccountsPayableDocument)accountingDocument)) {
return true;
}
}
return false;
}
/**
*
* @param accountingDocument
* @return
*/
private FinancialSystemTransactionalDocumentPresentationController getPresentationController(AccountingDocument accountingDocument) {
final Class<? extends DocumentPresentationController> presentationControllerClass = ((TransactionalDocumentEntry)SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(accountingDocument.getClass().getName())).getDocumentPresentationControllerClass();
FinancialSystemTransactionalDocumentPresentationController presentationController = null;
try {
presentationController = (FinancialSystemTransactionalDocumentPresentationController)presentationControllerClass.newInstance();
}
catch (InstantiationException ie) {
throw new RuntimeException("Cannot instantiate instance of presentation controller for "+accountingDocument.getClass().getName(), ie);
}
catch (IllegalAccessException iae) {
throw new RuntimeException("Cannot instantiate instance of presentation controller for "+accountingDocument.getClass().getName(), iae);
}
return presentationController;
}
/**
*
* @param accountingDocument
* @return
*/
@Override
protected FinancialSystemTransactionalDocumentAuthorizerBase getDocumentAuthorizer(AccountingDocument accountingDocument) {
final Class<? extends DocumentAuthorizer> documentAuthorizerClass = ((TransactionalDocumentEntry)SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDictionaryObjectEntry(accountingDocument.getClass().getName())).getDocumentAuthorizerClass();
FinancialSystemTransactionalDocumentAuthorizerBase documentAuthorizer = null;
try {
documentAuthorizer = (FinancialSystemTransactionalDocumentAuthorizerBase)documentAuthorizerClass.newInstance();
}
catch (InstantiationException ie) {
throw new RuntimeException("Cannot instantiate instance of document authorizer for "+accountingDocument.getClass().getName(), ie);
}
catch (IllegalAccessException iae) {
throw new RuntimeException("Cannot instantiate instance of document authorizer for "+accountingDocument.getClass().getName(), iae);
}
return documentAuthorizer;
}
@Override
public boolean isGroupEditable(AccountingDocument accountingDocument,
List<? extends AccountingLineRenderingContext> accountingLineRenderingContexts,
Person currentUser) {
boolean isEditable = super.isGroupEditable(accountingDocument, accountingLineRenderingContexts, currentUser);
if (isEditable){
if (accountingLineRenderingContexts.size() == 0) {
return false;
}
isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLineRenderingContexts.get(0).getAccountingLine());
}
return isEditable;
}
@Override
public boolean determineEditPermissionOnField(AccountingDocument accountingDocument,
AccountingLine accountingLine,
String accountingLineCollectionProperty,
String fieldName,
boolean editablePage) {
// the fields in a new line should be always editable
if (accountingLine.getSequenceNumber() == null) {
return true;
}
boolean isEditable = super.determineEditPermissionOnField(accountingDocument, accountingLine, accountingLineCollectionProperty,fieldName,editablePage);
if (isEditable){
isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLine);
}
return isEditable;
}
@Override
public boolean determineEditPermissionOnLine(AccountingDocument accountingDocument,
AccountingLine accountingLine,
String accountingLineCollectionProperty,
boolean currentUserIsDocumentInitiator,
boolean pageIsEditable) {
boolean isEditable = super.determineEditPermissionOnLine(accountingDocument, accountingLine, accountingLineCollectionProperty, currentUserIsDocumentInitiator, pageIsEditable);
if (isEditable){
isEditable = allowAccountingLinesAreEditable(accountingDocument,accountingLine);
}
return (isEditable && pageIsEditable);
}
/**
* This method checks whether the accounting lines are editable for a specific item type.
*
*/
protected boolean allowAccountingLinesAreEditable(AccountingDocument accountingDocument,
AccountingLine accountingLine){
PurApAccountingLine purapAccount = (PurApAccountingLine)accountingLine;
@SuppressWarnings("rawtypes")
Class clazz = getPurapDocumentClass(accountingDocument);
if (clazz == null){
return true;
}
//if not calculated yet then the line is editable
PurchasingAccountsPayableDocumentBase purapDoc = (PurchasingAccountsPayableDocumentBase) accountingDocument;
if (!purapDoc.isCalculated()) {
return true;
}
Collection<String> restrictedItemTypesList = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(clazz, PurapParameterConstants.PURAP_ITEM_TYPES_RESTRICTING_ACCOUNT_EDIT) );
// This call determines a new special case in which an item marked for trade-in cannot have editable accounting lines
// once the calculate button image is clicked, even if the accounting line has not been saved yet.
boolean retval = true;
retval = isEditableBasedOnTradeInRestriction(accountingDocument, accountingLine);
if (restrictedItemTypesList != null && purapAccount.getPurapItem() != null){
return (!restrictedItemTypesList.contains(purapAccount.getPurapItem().getItemTypeCode()) && retval);
} else if (restrictedItemTypesList != null && purapAccount.getPurapItem() == null) {
return retval;
} else {
return true;
}
}
/**
* Find the item to which an accounting line belongs. Convenience/Utility method.
*
* Some methods that require an accounting line with a purApItem attached were getting accounting lines
* passed in that did not yet have a purApItem. I needed a way to match the accounting line to the
* proper item.
*
* @param accountingDocument the document holding both the accounting line and the item to which the
* accounting line is attached
* @param accountingLine the accounting line of interest, for which a containing item should be found
* @return the item to which the incoming accounting line is attached
*/
protected PurApItem findTheItemForAccountingLine(AccountingDocument accountingDocument, AccountingLine accountingLine) {
PurApItem retval = null;
List<PurApItem> listItems = null;
scan: {
if (accountingDocument instanceof PurchasingAccountsPayableDocumentBase) {
listItems = ((PurchasingAccountsPayableDocumentBase) accountingDocument).getItems();
// loop through all items in the document to see if the item has an accounting line that
// matches the one passed in
for (PurApItem oneItem : listItems) {
List<PurApAccountingLine> acctLines = oneItem.getSourceAccountingLines();
for (PurApAccountingLine oneAcctLine : acctLines) {
// we want to compare the exact same memory location, not the contents
if (oneAcctLine == accountingLine) {
retval = oneItem;
// we found it, so I can stop altogether.
break scan;
}
}
}
}
}
return retval;
}
/**
* Handles a restriction on accounting lines assigned to trade-in items.
* If the accounting Line is for a trade-in item type, and the accounting line has contents,
* the user is not allowed to change the contents of the calculated values.
*
* The trade-in may not yet have a sequence number, so the old way of relying solely on sequence
* number (in method super.approvedForUnqualifiedEditing() is incomplete, and needs this extra check
* for trade-ins.
*
* @param accountingLine the accounting line being examined
* @return whether the accounting line is editable according to the trade-in/non-empty restriction
*/
private boolean isEditableBasedOnTradeInRestriction(AccountingDocument accountingDocument, AccountingLine accountingLine) {
boolean retval = true;
// if the accounting Line is for a trade-In, and the line has contents, the user is not allowed to
// change the contents of the calculated values
if ((accountingLine != null) && (accountingLine instanceof PurApAccountingLine)) {
PurApItem purApItem = (((PurApAccountingLine) accountingLine)).getPurapItem();
// this small block is to allow for another way to get to the purApItem if the
// incoming accounting line does not yet have a purApItem attached. Calling it is not
// completely necessary any more, unless/until the functional team members decide to
// add more item types to the read-only accounting lines list.
if (purApItem == null) {
purApItem = findTheItemForAccountingLine(accountingDocument, accountingLine);
}
if (purApItem != null) {
String itemTypeCode = purApItem.getItemTypeCode();
if (itemTypeCode.toUpperCase().equalsIgnoreCase(PurapParameterConstants.PURAP_ITEM_TYPE_TRDI)) {
// does the line have any contents? if so, then the user cannot edit them
if ((accountingLine.getChartOfAccountsCode() != null) || (accountingLine.getAccountNumber() != null) || (accountingLine.getFinancialObjectCode() != null)) {
retval = false;
}
}
// there has been a call to "if (purApItem.getItemAssignedToTradeInIndicator()) {" here
// that required the earlier use of findTheItemForAccountingLine()
}
}
return retval;
}
@SuppressWarnings("rawtypes")
private Class getPurapDocumentClass(AccountingDocument accountingDocument){
if (accountingDocument instanceof RequisitionDocument){
return RequisitionDocument.class;
}else if (accountingDocument instanceof PurchaseOrderDocument){
return PurchaseOrderDocument.class;
}else if (accountingDocument instanceof PaymentRequestDocument){
return PaymentRequestDocument.class;
}else{
return null;
}
}
/**
* Determines if the given line is editable, no matter what a KIM check would say about line editability. In the default case,
* any accounting line is editable - minus KIM check - when the document is PreRoute, or if the line is a new line
*
* This overriding implementation is required because the Purap module has a new restriction that requires
* that an accounting line for a Trade-In item cannot be manually editable, even if not yet saved ("not yet saved" means it has
* no sequence number). Therefore, the base implementation that determines editability on the sequence number alone has to be
* preceded by a check that declares ineligible for editing if it is a trade-in.
*
* @see org.kuali.kfs.module.purap.document.authorization.PurapAccountingLineAuthorizer#allowAccountingLinesAreEditable(AccountingDocument, AccountingLine)
*
* @param accountingDocument the accounting document the line is or wants to be associated with
* @param accountingLine the accounting line itself
* @param accountingLineCollectionProperty the collection the accounting line is or would be part of
* @param currentUserIsDocumentInitiator is the current user the initiator of the document?
* @return true if the line as a whole can be edited without the KIM check, false otherwise
*/
@Override
protected boolean approvedForUnqualifiedEditing(AccountingDocument accountingDocument, AccountingLine accountingLine, String accountingLineCollectionProperty, boolean currentUserIsDocumentInitiator) {
boolean retval = true;
retval = isEditableBasedOnTradeInRestriction(accountingDocument, accountingLine);
if (retval) {
retval = super.approvedForUnqualifiedEditing(accountingDocument, accountingLine, accountingLineCollectionProperty, currentUserIsDocumentInitiator);
}
return retval;
}
}