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.gl.web.struts;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.HashMap;
021    import java.util.List;
022    import java.util.Map;
023    import java.util.Properties;
024    
025    import javax.servlet.http.HttpServletRequest;
026    import javax.servlet.http.HttpServletResponse;
027    
028    import org.apache.commons.lang.StringUtils;
029    import org.apache.struts.action.ActionForm;
030    import org.apache.struts.action.ActionForward;
031    import org.apache.struts.action.ActionMapping;
032    import org.kuali.kfs.gl.GeneralLedgerConstants;
033    import org.kuali.kfs.gl.ObjectHelper;
034    import org.kuali.kfs.gl.businessobject.AccountBalance;
035    import org.kuali.kfs.gl.businessobject.lookup.AccountBalanceByConsolidationLookupableHelperServiceImpl;
036    import org.kuali.kfs.integration.ld.SegmentedBusinessObject;
037    import org.kuali.kfs.sys.KFSConstants;
038    import org.kuali.kfs.sys.KFSKeyConstants;
039    import org.kuali.kfs.sys.KFSPropertyConstants;
040    import org.kuali.kfs.sys.context.SpringContext;
041    import org.kuali.rice.kns.lookup.CollectionIncomplete;
042    import org.kuali.rice.kns.lookup.LookupResultsService;
043    import org.kuali.rice.kns.lookup.Lookupable;
044    import org.kuali.rice.kns.service.KNSServiceLocator;
045    import org.kuali.rice.kns.service.KualiConfigurationService;
046    import org.kuali.rice.kns.service.SequenceAccessorService;
047    import org.kuali.rice.kns.util.GlobalVariables;
048    import org.kuali.rice.kns.util.KNSUtils;
049    import org.kuali.rice.kns.util.KNSConstants;
050    import org.kuali.rice.kns.util.KualiDecimal;
051    import org.kuali.rice.kns.util.UrlFactory;
052    import org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction;
053    import org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm;
054    import org.kuali.rice.kns.web.ui.Column;
055    import org.kuali.rice.kns.web.ui.ResultRow;
056    
057    /**
058     * Balance inquiries are pretty much just lookups already, but are not used in the traditional sense. In most cases, balance
059     * inquiries only show the end-user data, and allow the end-user to drill-down into inquiries. A traditional lookup allows the user
060     * to return data to a form. This class is for balance inquiries implemented in the sense of a traditional lookup for forms that
061     * pull data out of inquiries.<br/> <br/> One example of this is the
062     * <code>{@link org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument}</code> which creates source lines from a labor
063     * ledger balance inquiry screen.<br/> <br/> This is a <code>{@link KualiMultipleValueLookupAction}</code> which required some
064     * customization because requirements were not possible with displaytag.
065     * 
066     * @see org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument
067     * @see org.kuali.kfs.module.ld.document.web.struts.SalaryExpenseTransferAction;
068     * @see org.kuali.kfs.module.ld.document.web.struts.SalaryExpenseTransferForm;
069     */
070    public class BalanceInquiryLookupAction extends KualiMultipleValueLookupAction {
071        private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(BalanceInquiryLookupAction.class);
072    
073        private static final String TOTALS_TABLE_KEY = "totalsTable";
074    
075        /**
076         * If there is no app param defined for the # rows/page, then this value will be used for the default
077         * 
078         * @see KualiMultipleValueLookupAction#getMaxRowsPerPage(MultipleValueLookupForm)
079         */
080        public static final int DEFAULT_MAX_ROWS_PER_PAGE = 50;
081    
082        private KualiConfigurationService kualiConfigurationService;
083        private String[] totalTitles;
084    
085        public BalanceInquiryLookupAction() {
086            super();
087            kualiConfigurationService = SpringContext.getBean(KualiConfigurationService.class);
088        }
089    
090        private void setTotalTitles() {
091            totalTitles = new String[7];
092    
093            totalTitles[0] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.INCOME);
094            totalTitles[1] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.INCOME_FROM_TRANSFERS);
095            totalTitles[2] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.INCOME_TOTAL);
096            totalTitles[3] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.EXPENSE);
097            totalTitles[4] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.EXPENSE_FROM_TRANSFERS);
098            totalTitles[5] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.EXPENSE_TOTAL);
099            totalTitles[6] = kualiConfigurationService.getPropertyString(KFSKeyConstants.AccountBalanceService.TOTAL);
100    
101        }
102    
103        private String[] getTotalTitles() {
104            if (null == totalTitles) {
105                setTotalTitles();
106            }
107    
108            return totalTitles;
109        }
110    
111        /**
112         * search - sets the values of the data entered on the form on the jsp into a map and then searches for the results.
113         */
114        public ActionForward search(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
115            BalanceInquiryLookupForm lookupForm = (BalanceInquiryLookupForm) form;
116            Lookupable lookupable = lookupForm.getLookupable();
117    
118            if (lookupable == null) {
119                LOG.error("Lookupable is null.");
120                throw new RuntimeException("Lookupable is null.");
121            }
122    
123            Collection displayList = new ArrayList();
124            CollectionIncomplete incompleteDisplayList;
125            List<ResultRow> resultTable = new ArrayList<ResultRow>();
126            Long totalSize;
127            boolean bounded = true;
128    
129            lookupable.validateSearchParameters(lookupForm.getFields());
130    
131            displayList = performMultipleValueLookup(lookupForm, resultTable, getMaxRowsPerPage(lookupForm), bounded);
132            incompleteDisplayList = (CollectionIncomplete) displayList;
133            totalSize = incompleteDisplayList.getActualSizeIfTruncated();
134    
135            if (lookupable.isSearchUsingOnlyPrimaryKeyValues()) {
136                lookupForm.setSearchUsingOnlyPrimaryKeyValues(true);
137                lookupForm.setPrimaryKeyFieldLabels(lookupable.getPrimaryKeyFieldLabels());
138            }
139            else {
140                lookupForm.setSearchUsingOnlyPrimaryKeyValues(false);
141                lookupForm.setPrimaryKeyFieldLabels(KFSConstants.EMPTY_STRING);
142            }
143    
144    
145            // TODO: use inheritance instead of this if statement
146            if (lookupable.getLookupableHelperService() instanceof AccountBalanceByConsolidationLookupableHelperServiceImpl) {
147                Object[] resultTableAsArray = resultTable.toArray();
148                Collection totalsTable = new ArrayList();
149    
150                int arrayIndex = 0;
151    
152                try {
153                    for (int listIndex = 0; listIndex < incompleteDisplayList.size(); listIndex++) {
154                        AccountBalance balance = (AccountBalance) incompleteDisplayList.get(listIndex);
155                        boolean ok = ObjectHelper.isOneOf(balance.getTitle(), getTotalTitles());
156                        if (ok) {
157                            if (totalSize > 7) {
158                                totalsTable.add(resultTableAsArray[arrayIndex]);
159                            }
160                            resultTable.remove(resultTableAsArray[arrayIndex]);
161                            incompleteDisplayList.remove(balance);
162                        }
163                        arrayIndex++;
164                    }
165    
166                    request.setAttribute(TOTALS_TABLE_KEY, totalsTable);
167                    GlobalVariables.getUserSession().addObject(TOTALS_TABLE_KEY, totalsTable);
168                }
169                catch (NumberFormatException e) {
170                    GlobalVariables.getMessageMap().putError(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, KFSKeyConstants.ERROR_CUSTOM, new String[] { "Fiscal Year must be a four-digit number" });
171                }
172                catch (Exception e) {
173                    GlobalVariables.getMessageMap().putError(KFSConstants.DOCUMENT_ERRORS, KFSKeyConstants.ERROR_CUSTOM, new String[] { "Please report the server error." });
174                    LOG.error("Application Errors", e);
175                }
176            }
177    
178            request.setAttribute(KFSConstants.REQUEST_SEARCH_RESULTS_SIZE, totalSize);
179            request.setAttribute(KFSConstants.REQUEST_SEARCH_RESULTS, resultTable);
180            lookupForm.setResultsActualSize((int) totalSize.longValue());
181            lookupForm.setResultsLimitedSize(resultTable.size());
182    
183            if (lookupForm.isSegmented()) {
184                LOG.debug("I'm segmented");
185                request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE);
186            }
187    
188            if (request.getParameter(KFSConstants.SEARCH_LIST_REQUEST_KEY) != null) {
189                GlobalVariables.getUserSession().removeObject(request.getParameter(KFSConstants.SEARCH_LIST_REQUEST_KEY));
190                request.setAttribute(KFSConstants.SEARCH_LIST_REQUEST_KEY, GlobalVariables.getUserSession().addObject(resultTable));
191            }
192    
193            return mapping.findForward(KFSConstants.MAPPING_BASIC);
194        }
195    
196        /**
197         * This method returns none of the selected results and redirects back to the lookup caller.
198         * 
199         * @param mapping
200         * @param form must be an instance of MultipleValueLookupForm
201         * @param request
202         * @param response
203         * @return
204         * @throws Exception
205         */
206        public ActionForward prepareToReturnNone(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
207            MultipleValueLookupForm multipleValueLookupForm = (MultipleValueLookupForm) form;
208            prepareToReturnNone(multipleValueLookupForm);
209    
210            // build the parameters for the refresh url
211            Properties parameters = new Properties();
212            parameters.put(KFSConstants.DOC_FORM_KEY, multipleValueLookupForm.getFormKey());
213            parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, KFSConstants.RETURN_METHOD_TO_CALL);
214            parameters.put(KFSConstants.REFRESH_CALLER, KFSConstants.MULTIPLE_VALUE);
215            parameters.put(KFSConstants.ANCHOR, multipleValueLookupForm.getLookupAnchor());
216    
217            String backUrl = UrlFactory.parameterizeUrl(multipleValueLookupForm.getBackLocation(), parameters);
218            return new ActionForward(backUrl, true);
219        }
220    
221        /**
222         * This method does the processing necessary to return selected results and sends a redirect back to the lookup caller
223         * 
224         * @param mapping
225         * @param form must be an instance of MultipleValueLookupForm
226         * @param request
227         * @param response
228         * @return
229         * @throws Exception
230         */
231        public ActionForward prepareToReturnSelectedResults(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
232            MultipleValueLookupForm multipleValueLookupForm = (MultipleValueLookupForm) form;
233            if (StringUtils.isBlank(multipleValueLookupForm.getLookupResultsSequenceNumber())) {
234                // no search was executed
235                return prepareToReturnNone(mapping, form, request, response);
236            }
237    
238            prepareToReturnSelectedResultBOs(multipleValueLookupForm);
239    
240            // build the parameters for the refresh url
241            Properties parameters = new Properties();
242            parameters.put(KFSConstants.LOOKUP_RESULTS_BO_CLASS_NAME, multipleValueLookupForm.getBusinessObjectClassName());
243            parameters.put(KFSConstants.LOOKUP_RESULTS_SEQUENCE_NUMBER, multipleValueLookupForm.getLookupResultsSequenceNumber());
244            parameters.put(KFSConstants.DOC_FORM_KEY, multipleValueLookupForm.getFormKey());
245            parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, KFSConstants.RETURN_METHOD_TO_CALL);
246            parameters.put(KFSConstants.REFRESH_CALLER, KFSConstants.MULTIPLE_VALUE);
247            parameters.put(KFSConstants.ANCHOR, multipleValueLookupForm.getLookupAnchor());
248            String backUrl = UrlFactory.parameterizeUrl(multipleValueLookupForm.getBackLocation(), parameters);
249            return new ActionForward(backUrl, true);
250        }
251    
252        /**
253         * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#sort(org.apache.struts.action.ActionMapping,
254         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
255         */
256        @Override
257        public ActionForward sort(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
258            request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE);
259            return super.sort(mapping, form, request, response);
260        }
261    
262        /**
263         * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#selectAll(org.apache.struts.action.ActionMapping,
264         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
265         */
266        @Override
267        public ActionForward selectAll(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
268            request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE);
269            return super.selectAll(mapping, form, request, response);
270        }
271    
272        /**
273         * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#unselectAll(org.apache.struts.action.ActionMapping,
274         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
275         */
276        @Override
277        public ActionForward unselectAll(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
278            request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE);
279            return super.unselectAll(mapping, form, request, response);
280        }
281    
282        /**
283         * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#switchToPage(org.apache.struts.action.ActionMapping,
284         *      org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
285         */
286        @Override
287        public ActionForward switchToPage(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception {
288            request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE);
289            return super.switchToPage(mapping, form, request, response);
290        }
291    
292        /**
293         * This method performs the lookup and returns a collection of lookup items. Also initializes values in the form that will allow
294         * the multiple value lookup page to render
295         * 
296         * @param multipleValueLookupForm
297         * @param resultTable a list of result rows (used to generate what's shown in the UI). This list will be modified by this method
298         * @param maxRowsPerPage
299         * @param bounded whether the results will be bounded
300         * @return the list of result BOs, possibly bounded by size
301         */
302        protected Collection performMultipleValueLookup(MultipleValueLookupForm multipleValueLookupForm, List<ResultRow> resultTable, int maxRowsPerPage, boolean bounded) {
303            Lookupable lookupable = multipleValueLookupForm.getLookupable();
304            Collection displayList = lookupable.performLookup(multipleValueLookupForm, resultTable, bounded);
305    
306            List defaultSortColumns = lookupable.getDefaultSortColumns();
307            if (defaultSortColumns != null && !defaultSortColumns.isEmpty() && resultTable != null && !resultTable.isEmpty()) {
308                // there's a default sort order, just find the first sort column, and we can't go wrong
309                String firstSortColumn = (String) defaultSortColumns.get(0);
310    
311                // go thru the first result row to find the index of the column (more efficient than calling lookupable.getColumns since
312                // we don't have to recreate column list)
313                int firstSortColumnIdx = -1;
314                List<Column> columnsForFirstResultRow = resultTable.get(0).getColumns();
315                for (int i = 0; i < columnsForFirstResultRow.size(); i++) {
316                    if (StringUtils.equals(firstSortColumn, columnsForFirstResultRow.get(i).getPropertyName())) {
317                        firstSortColumnIdx = i;
318                        break;
319                    }
320                }
321                multipleValueLookupForm.setColumnToSortIndex(firstSortColumnIdx);
322            }
323            else {
324                // don't know how results were sorted, so we just say -1
325                multipleValueLookupForm.setColumnToSortIndex(-1);
326            }
327    
328            // we just performed the lookup, so we're on the first page (indexed from 0)
329            multipleValueLookupForm.jumpToFirstPage(resultTable.size(), maxRowsPerPage);
330    
331            SequenceAccessorService sequenceAccessorService = SpringContext.getBean(SequenceAccessorService.class);
332            String lookupResultsSequenceNumber = String.valueOf(sequenceAccessorService.getNextAvailableSequenceNumber(KNSConstants.LOOKUP_RESULTS_SEQUENCE));
333            multipleValueLookupForm.setLookupResultsSequenceNumber(lookupResultsSequenceNumber);
334            try {
335                LookupResultsService lookupResultsService = SpringContext.getBean(LookupResultsService.class);
336                lookupResultsService.persistResultsTable(lookupResultsSequenceNumber, resultTable, GlobalVariables.getUserSession().getPerson().getPrincipalId());
337            }
338            catch (Exception e) {
339                LOG.error("error occured trying to persist multiple lookup results", e);
340                throw new RuntimeException("error occured trying to persist multiple lookup results");
341            }
342    
343            // since new search, nothing's checked
344            multipleValueLookupForm.setCompositeObjectIdMap(new HashMap<String, String>());
345    
346            return displayList;
347        }
348    
349        /**
350         * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#selectAll(org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm,
351         *      int)
352         */
353        @Override
354        protected List<ResultRow> selectAll(MultipleValueLookupForm multipleValueLookupForm, int maxRowsPerPage) {
355            List<ResultRow> resultTable = null;
356            try {
357                LookupResultsService lookupResultsService = KNSServiceLocator.getLookupResultsService();
358                String lookupResultsSequenceNumber = multipleValueLookupForm.getLookupResultsSequenceNumber();
359    
360                resultTable = lookupResultsService.retrieveResultsTable(lookupResultsSequenceNumber, GlobalVariables.getUserSession().getPerson().getPrincipalId());
361            }
362            catch (Exception e) {
363                LOG.error("error occured trying to export multiple lookup results", e);
364                throw new RuntimeException("error occured trying to export multiple lookup results");
365            }
366    
367            Map<String, String> selectedObjectIds = this.getSelectedObjectIds(multipleValueLookupForm, resultTable);
368    
369            multipleValueLookupForm.jumpToPage(multipleValueLookupForm.getViewedPageNumber(), resultTable.size(), maxRowsPerPage);
370            multipleValueLookupForm.setColumnToSortIndex(Integer.parseInt(multipleValueLookupForm.getPreviouslySortedColumnIndex()));
371            multipleValueLookupForm.setCompositeObjectIdMap(selectedObjectIds);
372    
373            return resultTable;
374        }
375    
376        /**
377         * put all enties into select object map. This implmentation only deals with the money amount objects.
378         * 
379         * @param multipleValueLookupForm the given struts form
380         * @param resultTable the given result table that holds all data being presented
381         * @return the map containing all entries available for selection
382         */
383        private Map<String, String> getSelectedObjectIds(MultipleValueLookupForm multipleValueLookupForm, List<ResultRow> resultTable) {
384            String businessObjectClassName = multipleValueLookupForm.getBusinessObjectClassName();
385            SegmentedBusinessObject segmentedBusinessObject;
386            try {
387                segmentedBusinessObject = (SegmentedBusinessObject) Class.forName(multipleValueLookupForm.getBusinessObjectClassName()).newInstance();
388            }
389            catch (Exception e) {
390                throw new RuntimeException("Fail to create an object of " + businessObjectClassName + e);
391            }
392    
393            Map<String, String> selectedObjectIds = new HashMap<String, String>();
394            Collection<String> segmentedPropertyNames = segmentedBusinessObject.getSegmentedPropertyNames();
395            for (ResultRow row : resultTable) {
396                for (Column column : row.getColumns()) {
397                    String propertyName = column.getPropertyName();
398                    if (segmentedPropertyNames.contains(propertyName)) {
399                        String propertyValue = StringUtils.replace(column.getPropertyValue(), ",", "");
400                        KualiDecimal amount = new KualiDecimal(propertyValue);
401    
402                        if (amount.isNonZero()) {
403                            String objectId = row.getObjectId() + "." + propertyName + "." + KNSUtils.convertDecimalIntoInteger(amount);
404                            selectedObjectIds.put(objectId, objectId);
405                        }
406                    }
407                }
408            }
409    
410            return selectedObjectIds;
411        }
412    }
413