/* * 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.gl.web.struts; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang.StringUtils; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.kuali.kfs.gl.GeneralLedgerConstants; import org.kuali.kfs.gl.ObjectHelper; import org.kuali.kfs.gl.businessobject.AccountBalance; import org.kuali.kfs.gl.businessobject.lookup.AccountBalanceByConsolidationLookupableHelperServiceImpl; import org.kuali.kfs.integration.ld.SegmentedBusinessObject; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSKeyConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kns.lookup.LookupResultsService; import org.kuali.rice.kns.lookup.Lookupable; import org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction; import org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm; import org.kuali.rice.kns.web.ui.Column; import org.kuali.rice.kns.web.ui.ResultRow; import org.kuali.rice.krad.lookup.CollectionIncomplete; import org.kuali.rice.krad.service.SequenceAccessorService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADConstants; import org.kuali.rice.krad.util.KRADUtils; import org.kuali.rice.krad.util.UrlFactory; /** * Balance inquiries are pretty much just lookups already, but are not used in the traditional sense. In most cases, balance * inquiries only show the end-user data, and allow the end-user to drill-down into inquiries. A traditional lookup allows the user * to return data to a form. This class is for balance inquiries implemented in the sense of a traditional lookup for forms that * pull data out of inquiries.<br/> <br/> One example of this is the * <code>{@link org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument}</code> which creates source lines from a labor * ledger balance inquiry screen.<br/> <br/> This is a <code>{@link KualiMultipleValueLookupAction}</code> which required some * customization because requirements were not possible with displaytag. * * @see org.kuali.kfs.module.ld.document.SalaryExpenseTransferDocument * @see org.kuali.kfs.module.ld.document.web.struts.SalaryExpenseTransferAction; * @see org.kuali.kfs.module.ld.document.web.struts.SalaryExpenseTransferForm; */ public class BalanceInquiryLookupAction extends KualiMultipleValueLookupAction { private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(BalanceInquiryLookupAction.class); private static final String TOTALS_TABLE_KEY = "totalsTable"; /** * If there is no app param defined for the # rows/page, then this value will be used for the default * * @see KualiMultipleValueLookupAction#getMaxRowsPerPage(MultipleValueLookupForm) */ public static final int DEFAULT_MAX_ROWS_PER_PAGE = 50; private ConfigurationService kualiConfigurationService; private String[] totalTitles; public BalanceInquiryLookupAction() { super(); kualiConfigurationService = SpringContext.getBean(ConfigurationService.class); } private void setTotalTitles() { totalTitles = new String[7]; totalTitles[0] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.INCOME); totalTitles[1] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.INCOME_FROM_TRANSFERS); totalTitles[2] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.INCOME_TOTAL); totalTitles[3] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.EXPENSE); totalTitles[4] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.EXPENSE_FROM_TRANSFERS); totalTitles[5] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.EXPENSE_TOTAL); totalTitles[6] = kualiConfigurationService.getPropertyValueAsString(KFSKeyConstants.AccountBalanceService.TOTAL); } private String[] getTotalTitles() { if (null == totalTitles) { setTotalTitles(); } return totalTitles; } /** * search - sets the values of the data entered on the form on the jsp into a map and then searches for the results. * * KRAD Conversion: Lookupable performs customization of the results if * balance inquiry. The result rows are added to a collection * based on field's actual size if truncated is > 7. * * Fields are in data dictionary for bo Balance. */ public ActionForward search(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { BalanceInquiryLookupForm lookupForm = (BalanceInquiryLookupForm) form; Lookupable lookupable = lookupForm.getLookupable(); if (lookupable == null) { LOG.error("Lookupable is null."); throw new RuntimeException("Lookupable is null."); } Collection displayList = new ArrayList(); CollectionIncomplete incompleteDisplayList; List<ResultRow> resultTable = new ArrayList<ResultRow>(); Long totalSize; boolean bounded = true; lookupable.validateSearchParameters(lookupForm.getFields()); displayList = performMultipleValueLookup(lookupForm, resultTable, getMaxRowsPerPage(lookupForm), bounded); incompleteDisplayList = (CollectionIncomplete) displayList; totalSize = incompleteDisplayList.getActualSizeIfTruncated(); if (lookupable.isSearchUsingOnlyPrimaryKeyValues()) { lookupForm.setSearchUsingOnlyPrimaryKeyValues(true); lookupForm.setPrimaryKeyFieldLabels(lookupable.getPrimaryKeyFieldLabels()); } else { lookupForm.setSearchUsingOnlyPrimaryKeyValues(false); lookupForm.setPrimaryKeyFieldLabels(KFSConstants.EMPTY_STRING); } // TODO: use inheritance instead of this if statement if (lookupable.getLookupableHelperService() instanceof AccountBalanceByConsolidationLookupableHelperServiceImpl) { Object[] resultTableAsArray = resultTable.toArray(); Collection totalsTable = new ArrayList(); int arrayIndex = 0; try { for (int listIndex = 0; listIndex < incompleteDisplayList.size(); listIndex++) { AccountBalance balance = (AccountBalance) incompleteDisplayList.get(listIndex); boolean ok = ObjectHelper.isOneOf(balance.getTitle(), getTotalTitles()); if (ok) { if (totalSize > 7) { totalsTable.add(resultTableAsArray[arrayIndex]); } resultTable.remove(resultTableAsArray[arrayIndex]); incompleteDisplayList.remove(balance); } arrayIndex++; } request.setAttribute(TOTALS_TABLE_KEY, totalsTable); GlobalVariables.getUserSession().addObject(TOTALS_TABLE_KEY, totalsTable); } catch (NumberFormatException e) { GlobalVariables.getMessageMap().putError(KFSPropertyConstants.UNIVERSITY_FISCAL_YEAR, KFSKeyConstants.ERROR_CUSTOM, new String[] { "Fiscal Year must be a four-digit number" }); } catch (Exception e) { GlobalVariables.getMessageMap().putError(KFSConstants.DOCUMENT_ERRORS, KFSKeyConstants.ERROR_CUSTOM, new String[] { "Please report the server error." }); LOG.error("Application Errors", e); } } request.setAttribute(KFSConstants.REQUEST_SEARCH_RESULTS_SIZE, totalSize); request.setAttribute(KFSConstants.REQUEST_SEARCH_RESULTS, resultTable); lookupForm.setResultsActualSize((int) totalSize.longValue()); lookupForm.setResultsLimitedSize(resultTable.size()); if (lookupForm.isSegmented()) { LOG.debug("I'm segmented"); request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE); } if (request.getParameter(KFSConstants.SEARCH_LIST_REQUEST_KEY) != null) { GlobalVariables.getUserSession().removeObject(request.getParameter(KFSConstants.SEARCH_LIST_REQUEST_KEY)); request.setAttribute(KFSConstants.SEARCH_LIST_REQUEST_KEY, GlobalVariables.getUserSession().addObjectWithGeneratedKey(resultTable)); } return mapping.findForward(KFSConstants.MAPPING_BASIC); } /** * This method returns none of the selected results and redirects back to the lookup caller. * * @param mapping * @param form must be an instance of MultipleValueLookupForm * @param request * @param response * @return * @throws Exception */ public ActionForward prepareToReturnNone(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { MultipleValueLookupForm multipleValueLookupForm = (MultipleValueLookupForm) form; prepareToReturnNone(multipleValueLookupForm); // build the parameters for the refresh url Properties parameters = new Properties(); parameters.put(KFSConstants.DOC_FORM_KEY, multipleValueLookupForm.getFormKey()); parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, KFSConstants.RETURN_METHOD_TO_CALL); parameters.put(KFSConstants.REFRESH_CALLER, KFSConstants.MULTIPLE_VALUE); parameters.put(KFSConstants.ANCHOR, multipleValueLookupForm.getLookupAnchor()); String backUrl = UrlFactory.parameterizeUrl(multipleValueLookupForm.getBackLocation(), parameters); return new ActionForward(backUrl, true); } /** * This method does the processing necessary to return selected results and sends a redirect back to the lookup caller * * @param mapping * @param form must be an instance of MultipleValueLookupForm * @param request * @param response * @return * @throws Exception */ public ActionForward prepareToReturnSelectedResults(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { MultipleValueLookupForm multipleValueLookupForm = (MultipleValueLookupForm) form; if (StringUtils.isBlank(multipleValueLookupForm.getLookupResultsSequenceNumber())) { // no search was executed return prepareToReturnNone(mapping, form, request, response); } prepareToReturnSelectedResultBOs(multipleValueLookupForm); // build the parameters for the refresh url Properties parameters = new Properties(); parameters.put(KFSConstants.LOOKUP_RESULTS_BO_CLASS_NAME, multipleValueLookupForm.getBusinessObjectClassName()); parameters.put(KFSConstants.LOOKUP_RESULTS_SEQUENCE_NUMBER, multipleValueLookupForm.getLookupResultsSequenceNumber()); parameters.put(KFSConstants.DOC_FORM_KEY, multipleValueLookupForm.getFormKey()); parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, KFSConstants.RETURN_METHOD_TO_CALL); parameters.put(KFSConstants.REFRESH_CALLER, KFSConstants.MULTIPLE_VALUE); parameters.put(KFSConstants.ANCHOR, multipleValueLookupForm.getLookupAnchor()); String backUrl = UrlFactory.parameterizeUrl(multipleValueLookupForm.getBackLocation(), parameters); return new ActionForward(backUrl, true); } /** * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#sort(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward sort(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE); return super.sort(mapping, form, request, response); } /** * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#selectAll(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward selectAll(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE); return super.selectAll(mapping, form, request, response); } /** * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#unselectAll(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward unselectAll(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE); return super.unselectAll(mapping, form, request, response); } /** * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#switchToPage(org.apache.struts.action.ActionMapping, * org.apache.struts.action.ActionForm, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ @Override public ActionForward switchToPage(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { request.setAttribute(GeneralLedgerConstants.LookupableBeanKeys.SEGMENTED_LOOKUP_FLAG_NAME, Boolean.TRUE); return super.switchToPage(mapping, form, request, response); } /** * This method performs the lookup and returns a collection of lookup items. Also initializes values in the form that will allow * the multiple value lookup page to render * * @param multipleValueLookupForm * @param resultTable a list of result rows (used to generate what's shown in the UI). This list will be modified by this method * @param maxRowsPerPage * @param bounded whether the results will be bounded * @return the list of result BOs, possibly bounded by size * * KRAD Conversion: Lookupable performs customization of the results if * balance inquiry. The multiple value results are sorted. * * Fields are in data dictionary for bo Balance. */ protected Collection performMultipleValueLookup(MultipleValueLookupForm multipleValueLookupForm, List<ResultRow> resultTable, int maxRowsPerPage, boolean bounded) { Lookupable lookupable = multipleValueLookupForm.getLookupable(); Collection displayList = lookupable.performLookup(multipleValueLookupForm, resultTable, bounded); List defaultSortColumns = lookupable.getDefaultSortColumns(); if (defaultSortColumns != null && !defaultSortColumns.isEmpty() && resultTable != null && !resultTable.isEmpty()) { // there's a default sort order, just find the first sort column, and we can't go wrong String firstSortColumn = (String) defaultSortColumns.get(0); // go thru the first result row to find the index of the column (more efficient than calling lookupable.getColumns since // we don't have to recreate column list) int firstSortColumnIdx = -1; List<Column> columnsForFirstResultRow = resultTable.get(0).getColumns(); for (int i = 0; i < columnsForFirstResultRow.size(); i++) { if (StringUtils.equals(firstSortColumn, columnsForFirstResultRow.get(i).getPropertyName())) { firstSortColumnIdx = i; break; } } multipleValueLookupForm.setColumnToSortIndex(firstSortColumnIdx); } else { // don't know how results were sorted, so we just say -1 multipleValueLookupForm.setColumnToSortIndex(-1); } // we just performed the lookup, so we're on the first page (indexed from 0) multipleValueLookupForm.jumpToFirstPage(resultTable.size(), maxRowsPerPage); SequenceAccessorService sequenceAccessorService = SpringContext.getBean(SequenceAccessorService.class); String lookupResultsSequenceNumber = String.valueOf(sequenceAccessorService.getNextAvailableSequenceNumber(KRADConstants.LOOKUP_RESULTS_SEQUENCE)); multipleValueLookupForm.setLookupResultsSequenceNumber(lookupResultsSequenceNumber); try { LookupResultsService lookupResultsService = SpringContext.getBean(LookupResultsService.class); lookupResultsService.persistResultsTable(lookupResultsSequenceNumber, resultTable, GlobalVariables.getUserSession().getPerson().getPrincipalId()); } catch (Exception e) { LOG.error("error occured trying to persist multiple lookup results", e); throw new RuntimeException("error occured trying to persist multiple lookup results"); } // since new search, nothing's checked multipleValueLookupForm.setCompositeObjectIdMap(new HashMap<String, String>()); return displayList; } /** * @see org.kuali.rice.kns.web.struts.action.KualiMultipleValueLookupAction#selectAll(org.kuali.rice.kns.web.struts.form.MultipleValueLookupForm, * int) * * KRAD Conversion: Lookupable performs customization of the results. * * Fields are in data dictionary for bo Balance. */ @Override protected List<ResultRow> selectAll(MultipleValueLookupForm multipleValueLookupForm, int maxRowsPerPage) { List<ResultRow> resultTable = null; try { LookupResultsService lookupResultsService = SpringContext.getBean(LookupResultsService.class); String lookupResultsSequenceNumber = multipleValueLookupForm.getLookupResultsSequenceNumber(); resultTable = lookupResultsService.retrieveResultsTable(lookupResultsSequenceNumber, GlobalVariables.getUserSession().getPerson().getPrincipalId()); } catch (Exception e) { LOG.error("error occured trying to export multiple lookup results", e); throw new RuntimeException("error occured trying to export multiple lookup results"); } Map<String, String> selectedObjectIds = this.getSelectedObjectIds(multipleValueLookupForm, resultTable); multipleValueLookupForm.jumpToPage(multipleValueLookupForm.getViewedPageNumber(), resultTable.size(), maxRowsPerPage); multipleValueLookupForm.setColumnToSortIndex(Integer.parseInt(multipleValueLookupForm.getPreviouslySortedColumnIndex())); multipleValueLookupForm.setCompositeObjectIdMap(selectedObjectIds); return resultTable; } /** * put all enties into select object map. This implmentation only deals with the money amount objects. * * @param multipleValueLookupForm the given struts form * @param resultTable the given result table that holds all data being presented * @return the map containing all entries available for selection * * KRAD Conversion: Performs customization of the results. Prepares * * There is no use of data dictionary for fields. */ private Map<String, String> getSelectedObjectIds(MultipleValueLookupForm multipleValueLookupForm, List<ResultRow> resultTable) { String businessObjectClassName = multipleValueLookupForm.getBusinessObjectClassName(); SegmentedBusinessObject segmentedBusinessObject; try { segmentedBusinessObject = (SegmentedBusinessObject) Class.forName(multipleValueLookupForm.getBusinessObjectClassName()).newInstance(); } catch (Exception e) { throw new RuntimeException("Fail to create an object of " + businessObjectClassName + e); } Map<String, String> selectedObjectIds = new HashMap<String, String>(); Collection<String> segmentedPropertyNames = segmentedBusinessObject.getSegmentedPropertyNames(); for (ResultRow row : resultTable) { for (Column column : row.getColumns()) { String propertyName = column.getPropertyName(); if (segmentedPropertyNames.contains(propertyName)) { String propertyValue = StringUtils.replace(column.getPropertyValue(), ",", ""); KualiDecimal amount = new KualiDecimal(propertyValue); if (amount.isNonZero()) { String objectId = row.getObjectId() + "." + propertyName + "." + KRADUtils.convertDecimalIntoInteger(amount); selectedObjectIds.put(objectId, objectId); } } } } return selectedObjectIds; } }