001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.sys.document.workflow;
017    
018    import java.util.ArrayList;
019    import java.util.Arrays;
020    import java.util.HashMap;
021    import java.util.Iterator;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.lang.StringUtils;
026    import org.kuali.kfs.coa.businessobject.Account;
027    import org.kuali.kfs.coa.businessobject.Organization;
028    import org.kuali.kfs.integration.ld.LaborLedgerPendingEntryForSearching;
029    import org.kuali.kfs.integration.ld.LaborLedgerPostingDocumentForSearching;
030    import org.kuali.kfs.sys.KFSPropertyConstants;
031    import org.kuali.kfs.sys.businessobject.AccountingLine;
032    import org.kuali.kfs.sys.businessobject.FinancialSystemDocumentHeader;
033    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
034    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
035    import org.kuali.kfs.sys.context.SpringContext;
036    import org.kuali.kfs.sys.document.AccountingDocument;
037    import org.kuali.kfs.sys.document.AmountTotaling;
038    import org.kuali.kfs.sys.document.GeneralLedgerPostingDocument;
039    import org.kuali.kfs.sys.document.datadictionary.AccountingLineGroupDefinition;
040    import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
041    import org.kuali.rice.kew.docsearch.DocumentSearchContext;
042    import org.kuali.rice.kew.docsearch.SearchableAttribute;
043    import org.kuali.rice.kew.docsearch.SearchableAttributeFloatValue;
044    import org.kuali.rice.kew.docsearch.SearchableAttributeStringValue;
045    import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
046    import org.kuali.rice.kew.exception.WorkflowException;
047    import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
048    import org.kuali.rice.kns.bo.BusinessObject;
049    import org.kuali.rice.kns.datadictionary.DocumentEntry;
050    import org.kuali.rice.kns.document.Document;
051    import org.kuali.rice.kns.lookup.LookupUtils;
052    import org.kuali.rice.kns.service.DataDictionaryService;
053    import org.kuali.rice.kns.service.DictionaryValidationService;
054    import org.kuali.rice.kns.service.DocumentService;
055    import org.kuali.rice.kns.service.KNSServiceLocator;
056    import org.kuali.rice.kns.util.FieldUtils;
057    import org.kuali.rice.kns.util.ObjectUtils;
058    import org.kuali.rice.kns.web.ui.Field;
059    import org.kuali.rice.core.util.KeyLabelPair;
060    import org.kuali.rice.kns.web.ui.Row;
061    import org.kuali.rice.kns.workflow.attribute.DataDictionarySearchableAttribute;
062    
063    public class FinancialSystemSearchableAttribute extends DataDictionarySearchableAttribute {
064    
065        //used to map the special fields to the DD Entry that validate it.
066        private static Map<String, String> magicFields = new HashMap<String, String>();
067        
068        static {
069            magicFields.put("chartOfAccountsCode","SourceAccountingLine");
070            magicFields.put("organizationCode","Organization");
071            magicFields.put("accountNumber","SourceAccountingLine");
072            magicFields.put("financialDocumentTypeCode","GeneralLedgerPendingEntry");
073            magicFields.put("financialDocumentTotalAmount","FinancialSystemDocumentHeader");
074        }
075        
076        
077        public List<Row> getSearchingRows(DocumentSearchContext documentSearchContext) {
078            DataDictionaryService ddService = SpringContext.getBean(DataDictionaryService.class);
079    
080            List<Row> docSearchRows = super.getSearchingRows(documentSearchContext);
081            
082            DocumentEntry entry = ddService.getDataDictionary().getDocumentEntry(documentSearchContext.getDocumentTypeName());
083            
084            if (entry == null) {
085                return docSearchRows;
086            }
087            Class<? extends Document> docClass = entry.getDocumentClass();
088     
089            List<String> displayedFieldNames = new ArrayList<String>();
090            
091            if (AccountingDocument.class.isAssignableFrom(docClass)) {
092                Map<String, AccountingLineGroupDefinition> alGroups = ((FinancialSystemTransactionalDocumentEntry)entry).getAccountingLineGroups();
093                Class alClass = SourceAccountingLine.class;
094    
095                if (ObjectUtils.isNotNull(alGroups)) {
096                    if (alGroups.containsKey("source")) {
097                        alClass = alGroups.get("source").getAccountingLineClass();
098                    }
099                }
100                
101                BusinessObject alBusinessObject  = null;
102    
103                Class orgClass = Organization.class;
104                BusinessObject orgBusinessObject  = null;
105                
106                try {
107                    alBusinessObject = (BusinessObject)alClass.newInstance();
108                    orgBusinessObject = (BusinessObject)orgClass.newInstance();
109                    
110                } catch (Exception cnfe) {
111                    throw new RuntimeException(cnfe);
112                }
113                
114                Field chartField = FieldUtils.getPropertyField(alClass, "chartOfAccountsCode", true);
115                chartField.setFieldDataType(SearchableAttribute.DATA_TYPE_STRING);
116                displayedFieldNames.add("chartOfAccountsCode");
117                LookupUtils.setFieldQuickfinder(alBusinessObject, "chartOfAccountsCode", chartField, displayedFieldNames);
118                
119                Field orgField = FieldUtils.getPropertyField(orgClass, "organizationCode", true);
120                orgField.setFieldDataType(SearchableAttribute.DATA_TYPE_STRING);
121                displayedFieldNames.clear();
122                displayedFieldNames.add("organizationCode");
123                LookupUtils.setFieldQuickfinder(new Account(), "organizationCode", orgField, displayedFieldNames);
124                
125                Field accountField = FieldUtils.getPropertyField(alClass, "accountNumber", true);
126                accountField.setFieldDataType(SearchableAttribute.DATA_TYPE_STRING);
127                displayedFieldNames.clear();
128                displayedFieldNames.add("accountNumber");
129                LookupUtils.setFieldQuickfinder(alBusinessObject, "accountNumber", accountField, displayedFieldNames);
130    
131                List<Field> fieldList = new ArrayList<Field>();
132                fieldList.add(chartField);
133                docSearchRows.add(new Row(fieldList));
134                
135                fieldList = new ArrayList<Field>();
136                fieldList.add(accountField);
137                docSearchRows.add(new Row(fieldList));
138                
139                fieldList = new ArrayList<Field>();
140                fieldList.add(orgField);
141                docSearchRows.add(new Row(fieldList));
142            }
143            
144            boolean displayedLedgerPostingDoc = false;
145            if (LaborLedgerPostingDocumentForSearching.class.isAssignableFrom(docClass)) {
146                Class boClass = GeneralLedgerPendingEntry.class;
147                
148                Field searchField = FieldUtils.getPropertyField(boClass, "financialDocumentTypeCode", true);
149                searchField.setFieldDataType(SearchableAttribute.DATA_TYPE_STRING);
150              
151                displayedFieldNames.clear();
152                displayedFieldNames.add("financialDocumentTypeCode");
153                LookupUtils.setFieldQuickfinder(new GeneralLedgerPendingEntry(), "financialDocumentTypeCode", searchField, displayedFieldNames);
154    
155                List<Field> fieldList = new ArrayList<Field>();
156                fieldList.add(searchField);
157                docSearchRows.add(new Row(fieldList));
158                displayedLedgerPostingDoc = true;
159            }
160            
161            if (GeneralLedgerPostingDocument.class.isAssignableFrom(docClass) && !displayedLedgerPostingDoc) {
162                Class boClass = GeneralLedgerPendingEntry.class;
163                
164                Field searchField = FieldUtils.getPropertyField(boClass, "financialDocumentTypeCode", true);
165                searchField.setFieldDataType(SearchableAttribute.DATA_TYPE_STRING);
166                
167                displayedFieldNames.clear();
168                displayedFieldNames.add("financialDocumentTypeCode");
169                LookupUtils.setFieldQuickfinder(new GeneralLedgerPendingEntry(), "financialDocumentTypeCode", searchField, displayedFieldNames);
170    
171                List<Field> fieldList = new ArrayList<Field>();
172                fieldList.add(searchField);
173                docSearchRows.add(new Row(fieldList));
174                
175            }
176            
177            if (AmountTotaling.class.isAssignableFrom( docClass)) {
178                  Class boClass = FinancialSystemDocumentHeader.class;
179                  
180                  Field searchField = FieldUtils.getPropertyField(boClass, "financialDocumentTotalAmount", true);
181                  searchField.setFieldDataType(SearchableAttribute.DATA_TYPE_FLOAT);
182    
183                  List<Field> fieldList = new ArrayList<Field>();
184                  fieldList.add(searchField);
185                  docSearchRows.add(new Row(fieldList));
186                  
187              }
188            
189           
190            
191            Row resultType = createSearchResultReturnRow();
192            docSearchRows.add(resultType);
193            return docSearchRows;
194        }
195        
196        public List<SearchableAttributeValue> getSearchStorageValues(DocumentSearchContext documentSearchContext) {
197            List<SearchableAttributeValue> searchAttrValues =  super.getSearchStorageValues(documentSearchContext);
198            
199            String docId = documentSearchContext.getDocumentId();
200            DocumentService docService = SpringContext.getBean(DocumentService.class);
201            Document doc = null;
202            try  {
203                doc = docService.getByDocumentHeaderIdSessionless(docId);
204            } catch (WorkflowException we) {
205                
206            }
207            
208            if (doc instanceof AmountTotaling) {
209                SearchableAttributeFloatValue searchableAttributeValue = new SearchableAttributeFloatValue();
210                searchableAttributeValue.setSearchableAttributeKey("financialDocumentTotalAmount");
211                searchableAttributeValue.setSearchableAttributeValue(((AmountTotaling)doc).getTotalDollarAmount().bigDecimalValue());
212                searchAttrValues.add(searchableAttributeValue);
213            }
214            
215            if (doc instanceof AccountingDocument) {
216                AccountingDocument accountingDoc = (AccountingDocument)doc;
217                searchAttrValues.addAll(harvestAccountingDocumentSearchableAttributes(accountingDoc));
218            }
219        
220            boolean indexedLedgerDoc = false;
221            if (doc instanceof LaborLedgerPostingDocumentForSearching) {
222                LaborLedgerPostingDocumentForSearching LLPostingDoc = (LaborLedgerPostingDocumentForSearching)doc;
223                searchAttrValues.addAll(harvestLLPDocumentSearchableAttributes(LLPostingDoc));
224                indexedLedgerDoc = true;
225            }
226            
227            if (doc instanceof GeneralLedgerPostingDocument && !indexedLedgerDoc) {
228                GeneralLedgerPostingDocument GLPostingDoc = (GeneralLedgerPostingDocument)doc;
229                searchAttrValues.addAll(harvestGLPDocumentSearchableAttributes(GLPostingDoc));
230            }
231            
232           
233            return searchAttrValues;
234        }
235        
236        /**
237         * 
238         * @see org.kuali.rice.kns.workflow.attribute.DataDictionarySearchableAttribute#validateUserSearchInputs(java.util.Map, org.kuali.rice.kew.docsearch.DocumentSearchContext)
239         */
240        
241        @Override
242        public List<WorkflowAttributeValidationError> validateUserSearchInputs(Map<Object, Object> paramMap, DocumentSearchContext searchContext) {
243            // this list is irrelevant. the validation errors are put on the stack in the validationService.
244            List<WorkflowAttributeValidationError> errors =  super.validateUserSearchInputs(paramMap, searchContext);
245            
246            DictionaryValidationService validationService = KNSServiceLocator.getDictionaryValidationService();
247            
248            for (Object key : paramMap.keySet()) {
249                String value = (String)paramMap.get(key);
250                
251                if (!StringUtils.isEmpty(value)) {
252                    
253                    if (magicFields.containsKey(key)) {
254                        validationService.validateAttributeFormat(magicFields.get(key), (String)key, value, (String)key);
255                    }
256                    
257                }
258                
259            }
260            return errors;
261        }
262        
263        
264        /**
265         * Harvest chart of accounts code, account number, and organization code as searchable attributes from an accounting document
266         * @param accountingDoc the accounting document to pull values from
267         * @return a List of searchable values
268         */
269        protected List<SearchableAttributeValue> harvestAccountingDocumentSearchableAttributes(AccountingDocument accountingDoc) {
270            List<SearchableAttributeValue> searchAttrValues = new ArrayList<SearchableAttributeValue>();
271            
272            for (Iterator itr = accountingDoc.getSourceAccountingLines().iterator(); itr.hasNext();) {
273                AccountingLine accountingLine = (AccountingLine)itr.next();
274                addSearchableAttributesForAccountingLine(searchAttrValues, accountingLine);
275            }
276            for (Iterator itr = accountingDoc.getTargetAccountingLines().iterator(); itr.hasNext();) {
277                AccountingLine accountingLine = (AccountingLine)itr.next();
278                addSearchableAttributesForAccountingLine(searchAttrValues, accountingLine);
279            }
280            
281            return searchAttrValues;
282        }
283        
284        /**
285         * Harvest GLPE document type as searchable attributes from a GL posting document
286         * @param GLPDoc the GLP document to pull values from
287         * @return a List of searchable values
288         */
289        protected List<SearchableAttributeValue> harvestGLPDocumentSearchableAttributes(GeneralLedgerPostingDocument GLPDoc) {
290            List<SearchableAttributeValue> searchAttrValues = new ArrayList<SearchableAttributeValue>();
291            
292            for (Iterator itr = GLPDoc.getGeneralLedgerPendingEntries().iterator(); itr.hasNext();) {
293                GeneralLedgerPendingEntry glpe = (GeneralLedgerPendingEntry)itr.next();
294                addSearchableAttributesForGLPE(searchAttrValues, glpe);
295            }
296            return searchAttrValues;
297        }
298        
299        /**
300         * Harvest LLPE document type as searchable attributes from a LL posting document
301         * @param LLPDoc the LLP document to pull values from
302         * @return a List of searchable values
303         */
304        protected List<SearchableAttributeValue> harvestLLPDocumentSearchableAttributes(LaborLedgerPostingDocumentForSearching LLPDoc) {
305            List<SearchableAttributeValue> searchAttrValues = new ArrayList<SearchableAttributeValue>();
306            
307            for (Iterator itr = LLPDoc.getLaborLedgerPendingEntriesForSearching().iterator(); itr.hasNext();) {
308                LaborLedgerPendingEntryForSearching llpe = (LaborLedgerPendingEntryForSearching)itr.next();
309                addSearchableAttributesForLLPE(searchAttrValues, llpe);
310            }
311            return searchAttrValues;
312        }
313        
314        
315        /**
316         * Pulls the default searchable attributes - chart code, account number, and account organization code - from a given accounting line and populates
317         * the searchable attribute values in the given list
318         * @param searchAttrValues a List of SearchableAttributeValue objects to populate
319         * @param accountingLine an AccountingLine to get values from
320         */
321        protected void addSearchableAttributesForAccountingLine(List<SearchableAttributeValue> searchAttrValues, AccountingLine accountingLine) {
322            SearchableAttributeStringValue searchableAttributeValue = new SearchableAttributeStringValue();
323            searchableAttributeValue.setSearchableAttributeKey(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE);
324            searchableAttributeValue.setSearchableAttributeValue(accountingLine.getChartOfAccountsCode());
325            searchAttrValues.add(searchableAttributeValue);
326            
327            searchableAttributeValue = new SearchableAttributeStringValue();
328            searchableAttributeValue.setSearchableAttributeKey(KFSPropertyConstants.ACCOUNT_NUMBER);
329            searchableAttributeValue.setSearchableAttributeValue(accountingLine.getAccountNumber());
330            searchAttrValues.add(searchableAttributeValue);
331            
332            searchableAttributeValue = new SearchableAttributeStringValue();
333            searchableAttributeValue.setSearchableAttributeKey(KFSPropertyConstants.ORGANIZATION_CODE);
334            searchableAttributeValue.setSearchableAttributeValue(accountingLine.getAccount().getOrganizationCode());
335            searchAttrValues.add(searchableAttributeValue);
336        }
337        
338        /**
339         * Pulls the default searchable attribute - financialSystemTypeCode - from a given accounting line and populates
340         * the searchable attribute values in the given list
341         * @param searchAttrValues a List of SearchableAttributeValue objects to populate
342         * @param glpe a GeneralLedgerPendingEntry to get values from
343         */
344        protected void addSearchableAttributesForGLPE(List<SearchableAttributeValue> searchAttrValues, GeneralLedgerPendingEntry glpe) {
345            SearchableAttributeStringValue searchableAttributeValue = new SearchableAttributeStringValue();
346            searchableAttributeValue.setSearchableAttributeKey(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE);
347            searchableAttributeValue.setSearchableAttributeValue(glpe.getFinancialDocumentTypeCode());
348            searchAttrValues.add(searchableAttributeValue);
349            
350        }
351        
352        /**
353         * Pulls the default searchable attribute - financialSystemTypeCode from a given accounting line and populates
354         * the searchable attribute values in the given list
355         * @param searchAttrValues a List of SearchableAttributeValue objects to populate
356         * @param llpe a LaborLedgerPendingEntry to get values from
357         */
358        protected void addSearchableAttributesForLLPE(List<SearchableAttributeValue> searchAttrValues, LaborLedgerPendingEntryForSearching llpe) {
359            SearchableAttributeStringValue searchableAttributeValue = new SearchableAttributeStringValue();
360            searchableAttributeValue.setSearchableAttributeKey(KFSPropertyConstants.FINANCIAL_DOCUMENT_TYPE_CODE);
361            searchableAttributeValue.setSearchableAttributeValue(llpe.getFinancialDocumentTypeCode());
362            searchAttrValues.add(searchableAttributeValue);
363        }
364        
365        private Row createSearchResultReturnRow() {
366            String attributeName = "displayType";
367            Field searchField = new Field();
368            searchField.setPropertyName(attributeName);
369            searchField.setFieldType(Field.RADIO);
370            searchField.setFieldLabel("Search Result Type");
371            searchField.setIndexedForSearch(false);
372            searchField.setBusinessObjectClassName("");
373            searchField.setFieldHelpName("");
374            searchField.setFieldHelpSummary("");
375            searchField.setColumnVisible(false);
376            List<KeyLabelPair> values = new ArrayList<KeyLabelPair>();
377            values.add(new KeyLabelPair("document", "Document Specific Data"));
378            values.add(new KeyLabelPair("workflow", "Workflow Data"));
379            searchField.setFieldValidValues(values);
380            searchField.setPropertyValue("document");
381    
382            List<Field> fieldList = new ArrayList<Field>();
383            fieldList.add(searchField);
384    
385            return new Row(fieldList);
386            
387        }
388        
389    }