/* * 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.workflow; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.Account; import org.kuali.kfs.coa.businessobject.Organization; import org.kuali.kfs.integration.ld.LaborLedgerPendingEntryForSearching; import org.kuali.kfs.integration.ld.LaborLedgerPostingDocumentForSearching; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.AccountingLine; import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; import org.kuali.kfs.sys.businessobject.SourceAccountingLine; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.AccountingDocument; import org.kuali.kfs.sys.document.AmountTotaling; import org.kuali.kfs.sys.document.GeneralLedgerPostingDocument; import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition; import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry; import org.kuali.rice.core.api.uif.RemotableAttributeError; import org.kuali.rice.core.api.util.ConcreteKeyValue; import org.kuali.rice.core.api.util.KeyValue; import org.kuali.rice.kew.api.KewApiConstants.SearchableAttributeConstants; import org.kuali.rice.kew.api.document.DocumentWithContent; import org.kuali.rice.kew.api.document.attribute.DocumentAttribute; import org.kuali.rice.kew.api.document.attribute.DocumentAttributeDecimal; import org.kuali.rice.kew.api.document.attribute.DocumentAttributeString; import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kew.api.extension.ExtensionDefinition; import org.kuali.rice.kns.lookup.LookupUtils; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.kns.service.DictionaryValidationService; import org.kuali.rice.kns.util.FieldUtils; import org.kuali.rice.kns.web.ui.Field; import org.kuali.rice.kns.web.ui.Row; import org.kuali.rice.krad.bo.BusinessObject; import org.kuali.rice.krad.datadictionary.DocumentEntry; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.krad.workflow.attribute.DataDictionarySearchableAttribute; //RICE20 This class needs to be fixed to support pre-rice2.0 features public class FinancialSystemSearchableAttribute extends DataDictionarySearchableAttribute { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(FinancialSystemSearchableAttribute.class); protected static final String DISPLAY_TYPE_SEARCH_ATTRIBUTE_LABEL = "Search Result Type"; protected static final String WORKFLOW_DISPLAY_TYPE_LABEL = "Workflow Data"; protected static final String DOCUMENT_DISPLAY_TYPE_LABEL = "Document Specific Data"; protected static final String WORKFLOW_DISPLAY_TYPE_VALUE = "workflow"; protected static final String DOCUMENT_DISPLAY_TYPE_VALUE = "document"; protected static final String DISPLAY_TYPE_SEARCH_ATTRIBUTE_NAME = "displayType"; protected static final List<KeyValue> SEARCH_RESULT_TYPE_OPTION_LIST = new ArrayList<KeyValue>(2); static { SEARCH_RESULT_TYPE_OPTION_LIST.add(new ConcreteKeyValue(DOCUMENT_DISPLAY_TYPE_VALUE, DOCUMENT_DISPLAY_TYPE_LABEL)); SEARCH_RESULT_TYPE_OPTION_LIST.add(new ConcreteKeyValue(WORKFLOW_DISPLAY_TYPE_VALUE, WORKFLOW_DISPLAY_TYPE_LABEL)); } // used to map the special fields to the DD Entry that validate it. private static final Map<String, String> magicFields = new HashMap<String, String>(); static { magicFields.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, SourceAccountingLine.class.getSimpleName()); magicFields.put(KFSPropertyConstants.ORGANIZATION_CODE, Organization.class.getSimpleName()); magicFields.put(KFSPropertyConstants.ACCOUNT_NUMBER, SourceAccountingLine.class.getSimpleName()); magicFields.put(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, GeneralLedgerPendingEntry.class.getSimpleName()); magicFields.put(KFSPropertyConstants.FINANCIAL_DOCUMENT_TOTAL_AMOUNT, FinancialSystemDocumentHeader.class.getSimpleName() ); } @Override protected List<Row> getSearchingRows(String documentTypeName) { if ( LOG.isDebugEnabled() ) { LOG.debug( "getSearchingRows( " + documentTypeName + " )" ); if ( LOG.isTraceEnabled() ) { LOG.trace("Stack Trace at point of call", new Throwable()); } } DataDictionaryService ddService = SpringContext.getBean(DataDictionaryService.class); List<Row> docSearchRows = super.getSearchingRows(documentTypeName); DocumentEntry entry = ddService.getDataDictionary().getDocumentEntry(documentTypeName); if (entry == null) { return docSearchRows; } Class<? extends Document> docClass = entry.getDocumentClass(); if (AccountingDocument.class.isAssignableFrom(docClass)) { Map<String, AccountingLineGroupDefinition> alGroups = ((FinancialSystemTransactionalDocumentEntry) entry).getAccountingLineGroups(); Class alClass = SourceAccountingLine.class; if (ObjectUtils.isNotNull(alGroups)) { if (alGroups.containsKey("source")) { alClass = alGroups.get("source").getAccountingLineClass(); } } BusinessObject alBusinessObject = null; try { alBusinessObject = (BusinessObject) alClass.newInstance(); } catch (Exception cnfe) { throw new RuntimeException( "Unable to instantiate accounting line class: " + alClass, cnfe); } Field chartField = FieldUtils.getPropertyField(alClass, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, true); chartField.setFieldDataType(SearchableAttributeConstants.DATA_TYPE_STRING); chartField.setColumnVisible(false); LookupUtils.setFieldQuickfinder(alBusinessObject, KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartField, Collections.singletonList(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE)); docSearchRows.add(new Row(Collections.singletonList(chartField))); Field orgField = FieldUtils.getPropertyField(Organization.class, KFSPropertyConstants.ORGANIZATION_CODE, true); orgField.setFieldDataType(SearchableAttributeConstants.DATA_TYPE_STRING); orgField.setColumnVisible(false); LookupUtils.setFieldQuickfinder(new Account(), KFSPropertyConstants.ORGANIZATION_CODE, orgField, Collections.singletonList(KFSPropertyConstants.ORGANIZATION_CODE)); docSearchRows.add(new Row(Collections.singletonList(orgField))); Field accountField = FieldUtils.getPropertyField(alClass, KFSPropertyConstants.ACCOUNT_NUMBER, true); accountField.setFieldDataType(SearchableAttributeConstants.DATA_TYPE_STRING); accountField.setColumnVisible(false); LookupUtils.setFieldQuickfinder(alBusinessObject, KFSPropertyConstants.ACCOUNT_NUMBER, accountField, Collections.singletonList(KFSPropertyConstants.ACCOUNT_NUMBER)); docSearchRows.add(new Row(Collections.singletonList(accountField))); } boolean displayedLedgerPostingDoc = false; if (LaborLedgerPostingDocumentForSearching.class.isAssignableFrom(docClass)) { Field searchField = FieldUtils.getPropertyField(GeneralLedgerPendingEntry.class, KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, true); searchField.setFieldDataType(SearchableAttributeConstants.DATA_TYPE_STRING); LookupUtils.setFieldQuickfinder(new GeneralLedgerPendingEntry(), KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, searchField, Collections.singletonList(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE)); docSearchRows.add(new Row(Collections.singletonList(searchField))); displayedLedgerPostingDoc = true; } if (GeneralLedgerPostingDocument.class.isAssignableFrom(docClass) && !displayedLedgerPostingDoc) { Field searchField = FieldUtils.getPropertyField(GeneralLedgerPendingEntry.class, KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, true); searchField.setFieldDataType(SearchableAttributeConstants.DATA_TYPE_STRING); LookupUtils.setFieldQuickfinder(new GeneralLedgerPendingEntry(), KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE, searchField, Collections.singletonList(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE)); docSearchRows.add(new Row(Collections.singletonList(searchField))); } if (AmountTotaling.class.isAssignableFrom(docClass)) { Field searchField = FieldUtils.getPropertyField(FinancialSystemDocumentHeader.class, KFSPropertyConstants.FINANCIAL_DOCUMENT_TOTAL_AMOUNT, true); searchField.setFieldDataType(SearchableAttributeConstants.DATA_TYPE_FLOAT); docSearchRows.add(new Row(Collections.singletonList(searchField))); } Row resultType = createSearchResultDisplayTypeRow(); docSearchRows.add(resultType); if ( LOG.isDebugEnabled() ) { LOG.debug( "Returning Rows: " + docSearchRows ); } return docSearchRows; } @Override public List<DocumentAttribute> extractDocumentAttributes(ExtensionDefinition extensionDefinition, DocumentWithContent documentWithContent) { if (LOG.isDebugEnabled()) { LOG.debug("extractDocumentAttributes( " + extensionDefinition + ", " + documentWithContent + " )"); } List<DocumentAttribute> searchAttrValues = super.extractDocumentAttributes(extensionDefinition, documentWithContent); String docId = documentWithContent.getDocument().getDocumentId(); DocumentService docService = SpringContext.getBean(DocumentService.class); Document doc = null; try { doc = docService.getByDocumentHeaderIdSessionless(docId); } catch (WorkflowException we) { } if (doc != null) { if (doc instanceof AmountTotaling && ((AmountTotaling)doc).getTotalDollarAmount() != null) { DocumentAttributeDecimal.Builder searchableAttributeValue = DocumentAttributeDecimal.Builder.create(KFSPropertyConstants.FINANCIAL_DOCUMENT_TOTAL_AMOUNT); searchableAttributeValue.setValue(((AmountTotaling) doc).getTotalDollarAmount().bigDecimalValue()); searchAttrValues.add(searchableAttributeValue.build()); } if (doc instanceof AccountingDocument) { AccountingDocument accountingDoc = (AccountingDocument) doc; searchAttrValues.addAll(harvestAccountingDocumentSearchableAttributes(accountingDoc)); } boolean indexedLedgerDoc = false; if (doc instanceof LaborLedgerPostingDocumentForSearching) { LaborLedgerPostingDocumentForSearching LLPostingDoc = (LaborLedgerPostingDocumentForSearching) doc; searchAttrValues.addAll(harvestLLPDocumentSearchableAttributes(LLPostingDoc)); indexedLedgerDoc = true; } if (doc instanceof GeneralLedgerPostingDocument && !indexedLedgerDoc) { GeneralLedgerPostingDocument GLPostingDoc = (GeneralLedgerPostingDocument) doc; searchAttrValues.addAll(harvestGLPDocumentSearchableAttributes(GLPostingDoc)); } } return searchAttrValues; } @Override public List<RemotableAttributeError> validateDocumentAttributeCriteria(ExtensionDefinition extensionDefinition, DocumentSearchCriteria documentSearchCriteria) { if (LOG.isDebugEnabled()) { LOG.debug("validateDocumentAttributeCriteria( " + extensionDefinition + ", " + documentSearchCriteria + " )"); } // this list is irrelevant. the validation errors are put on the stack in the validationService. List<RemotableAttributeError> errors = super.validateDocumentAttributeCriteria(extensionDefinition, documentSearchCriteria); DictionaryValidationService validationService = SpringContext.getBean(DictionaryValidationService.class); Map<String,List<String>> paramMap = documentSearchCriteria.getDocumentAttributeValues(); for (String key : paramMap.keySet()) { List<String> values = paramMap.get(key); if ( values != null && !values.isEmpty() ) { for ( String value : values ) { if (!StringUtils.isEmpty(value)) { if (magicFields.containsKey(key)) { validationService.validateAttributeFormat(magicFields.get(key), key, value, key); } } } } } retrieveValidationErrorsFromGlobalVariables(errors); return errors; }; /** * Harvest chart of accounts code, account number, and organization code as searchable attributes from an accounting document * @param accountingDoc the accounting document to pull values from * @return a List of searchable values */ protected List<DocumentAttribute> harvestAccountingDocumentSearchableAttributes(AccountingDocument accountingDoc) { List<DocumentAttribute> searchAttrValues = new ArrayList<DocumentAttribute>(); for ( AccountingLine line : (List<AccountingLine>)accountingDoc.getSourceAccountingLines() ) { addSearchableAttributesForAccountingLine(searchAttrValues, line); } for ( AccountingLine line : (List<AccountingLine>)accountingDoc.getTargetAccountingLines() ) { addSearchableAttributesForAccountingLine(searchAttrValues, line); } return searchAttrValues; } /** * Harvest GLPE document type as searchable attributes from a GL posting document * @param GLPDoc the GLP document to pull values from * @return a List of searchable values */ protected List<DocumentAttribute> harvestGLPDocumentSearchableAttributes(GeneralLedgerPostingDocument doc) { List<DocumentAttribute> searchAttrValues = new ArrayList<DocumentAttribute>(); for ( GeneralLedgerPendingEntry glpe : doc.getGeneralLedgerPendingEntries() ) { addSearchableAttributesForGLPE(searchAttrValues, glpe); } return searchAttrValues; } /** * Harvest LLPE document type as searchable attributes from a LL posting document * @param LLPDoc the LLP document to pull values from * @return a List of searchable values */ protected List<DocumentAttribute> harvestLLPDocumentSearchableAttributes(LaborLedgerPostingDocumentForSearching LLPDoc) { List<DocumentAttribute> searchAttrValues = new ArrayList<DocumentAttribute>(); for (Iterator itr = LLPDoc.getLaborLedgerPendingEntriesForSearching().iterator(); itr.hasNext();) { LaborLedgerPendingEntryForSearching llpe = (LaborLedgerPendingEntryForSearching)itr.next(); addSearchableAttributesForLLPE(searchAttrValues, llpe); } return searchAttrValues; } /** * Pulls the default searchable attributes - chart code, account number, and account organization code - from a given accounting line and populates * the searchable attribute values in the given list * @param searchAttrValues a List of SearchableAttributeValue objects to populate * @param accountingLine an AccountingLine to get values from */ protected void addSearchableAttributesForAccountingLine(List<DocumentAttribute> searchAttrValues, AccountingLine accountingLine) { DocumentAttributeString.Builder searchableAttributeValue; if (!ObjectUtils.isNull(accountingLine)) { if (!StringUtils.isBlank(accountingLine.getChartOfAccountsCode())) { searchableAttributeValue = DocumentAttributeString.Builder.create(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE); searchableAttributeValue.setValue(accountingLine.getChartOfAccountsCode()); searchAttrValues.add(searchableAttributeValue.build()); } if (!StringUtils.isBlank(accountingLine.getAccountNumber())) { searchableAttributeValue = DocumentAttributeString.Builder.create(KFSPropertyConstants.ACCOUNT_NUMBER); searchableAttributeValue.setValue(accountingLine.getAccountNumber()); searchAttrValues.add(searchableAttributeValue.build()); } if (!ObjectUtils.isNull(accountingLine.getAccount()) && !StringUtils.isBlank(accountingLine.getAccount().getOrganizationCode())) { searchableAttributeValue = DocumentAttributeString.Builder.create(KFSPropertyConstants.ORGANIZATION_CODE); searchableAttributeValue.setValue(accountingLine.getAccount().getOrganizationCode()); searchAttrValues.add(searchableAttributeValue.build()); } } } /** * Pulls the default searchable attribute - financialSystemTypeCode - from a given accounting line and populates * the searchable attribute values in the given list * @param searchAttrValues a List of SearchableAttributeValue objects to populate * @param glpe a GeneralLedgerPendingEntry to get values from */ protected void addSearchableAttributesForGLPE(List<DocumentAttribute> searchAttrValues, GeneralLedgerPendingEntry glpe) { if (glpe != null && !StringUtils.isBlank(glpe.getFinancialDocumentTypeCode())) { DocumentAttributeString.Builder searchableAttributeValue = DocumentAttributeString.Builder.create(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE); searchableAttributeValue.setValue(glpe.getFinancialDocumentTypeCode()); searchAttrValues.add(searchableAttributeValue.build()); } } /** * Pulls the default searchable attribute - financialSystemTypeCode from a given accounting line and populates * the searchable attribute values in the given list * @param searchAttrValues a List of SearchableAttributeValue objects to populate * @param llpe a LaborLedgerPendingEntry to get values from */ protected void addSearchableAttributesForLLPE(List<DocumentAttribute> searchAttrValues, LaborLedgerPendingEntryForSearching llpe) { if (llpe != null && !StringUtils.isBlank(llpe.getFinancialDocumentTypeCode())) { DocumentAttributeString.Builder searchableAttributeValue = DocumentAttributeString.Builder.create(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE); searchableAttributeValue.setValue(llpe.getFinancialDocumentTypeCode()); searchAttrValues.add(searchableAttributeValue.build()); } } protected Row createSearchResultDisplayTypeRow() { Field searchField = new Field(DISPLAY_TYPE_SEARCH_ATTRIBUTE_NAME, DISPLAY_TYPE_SEARCH_ATTRIBUTE_LABEL); searchField.setFieldType(Field.RADIO); searchField.setIndexedForSearch(false); searchField.setBusinessObjectClassName(""); searchField.setFieldHelpName(""); searchField.setFieldHelpSummary(""); searchField.setColumnVisible(false); searchField.setFieldValidValues(SEARCH_RESULT_TYPE_OPTION_LIST); searchField.setPropertyValue(DOCUMENT_DISPLAY_TYPE_VALUE); searchField.setDefaultValue(DOCUMENT_DISPLAY_TYPE_VALUE); return new Row(Collections.singletonList(searchField)); } // RICE20: fixes to allow document search to function until Rice 2.0.1 // @Override // public List<RemotableAttributeField> getSearchFields(ExtensionDefinition extensionDefinition, String documentTypeName) { // if (LOG.isDebugEnabled()) { // LOG.debug("getSearchFields( " + extensionDefinition + ", " + documentTypeName + " )"); // } // List<Row> searchRows = getSearchingRows(documentTypeName); // for ( Row row : searchRows ) { // for ( Field field : row.getFields() ) { // if ( field.getFieldType().equals(Field.CURRENCY) ) { // field.setFieldType(Field.TEXT); // } // if ( field.getMaxLength() < 1 ) { // field.setMaxLength(100); // } // } // } // return FieldUtils.convertRowsToAttributeFields(searchRows); // } }