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.module.cab.businessobject.lookup; 017 018 import java.sql.Date; 019 import java.sql.Timestamp; 020 import java.util.ArrayList; 021 import java.util.Collection; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Properties; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.kuali.kfs.module.cab.CabConstants; 030 import org.kuali.kfs.module.cab.CabPropertyConstants; 031 import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry; 032 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument; 033 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableProcessingReport; 034 import org.kuali.kfs.module.cab.service.PurchasingAccountsPayableReportService; 035 import org.kuali.kfs.sys.KFSConstants; 036 import org.kuali.kfs.sys.context.SpringContext; 037 import org.kuali.rice.kim.bo.impl.KimAttributes; 038 import org.kuali.rice.kim.bo.types.dto.AttributeSet; 039 import org.kuali.rice.kim.service.KIMServiceLocator; 040 import org.kuali.rice.kim.util.KimConstants; 041 import org.kuali.rice.kns.bo.BusinessObject; 042 import org.kuali.rice.kns.lookup.CollectionIncomplete; 043 import org.kuali.rice.kns.lookup.HtmlData; 044 import org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl; 045 import org.kuali.rice.kns.lookup.LookupUtils; 046 import org.kuali.rice.kns.lookup.HtmlData.AnchorHtmlData; 047 import org.kuali.rice.kns.service.BusinessObjectService; 048 import org.kuali.rice.kns.util.GlobalVariables; 049 import org.kuali.rice.kns.util.KNSConstants; 050 import org.kuali.rice.kns.util.KualiDecimal; 051 import org.kuali.rice.kns.util.ObjectUtils; 052 import org.kuali.rice.kns.util.UrlFactory; 053 054 /** 055 * This class overrids the base getActionUrls method 056 */ 057 public class PurApReportLookupableHelperServiceImpl extends KualiLookupableHelperServiceImpl { 058 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurApReportLookupableHelperServiceImpl.class); 059 060 private PurchasingAccountsPayableReportService purApReportService; 061 062 /** 063 * Custom action urls for CAB PurAp lines. 064 * 065 * @see org.kuali.rice.kns.lookup.LookupableHelperService#getCustomActionUrls(org.kuali.rice.kns.bo.BusinessObject, 066 * java.util.List, java.util.List pkNames) 067 */ 068 @Override 069 public List<HtmlData> getCustomActionUrls(BusinessObject bo, List pkNames) { 070 AttributeSet permissionDetails = new AttributeSet(); 071 permissionDetails.put(KimAttributes.NAMESPACE_CODE, "KFS-CAB"); 072 permissionDetails.put(KimAttributes.ACTION_CLASS, "PurApLineAction"); 073 074 if (!KIMServiceLocator.getIdentityManagementService().isAuthorizedByTemplateName(GlobalVariables.getUserSession().getPrincipalId(), KNSConstants.KNS_NAMESPACE, KimConstants.PermissionTemplateNames.USE_SCREEN, permissionDetails, null)) { 075 return super.getEmptyActionUrls(); 076 } 077 078 GeneralLedgerEntry glEntry = (GeneralLedgerEntry) bo; 079 080 Properties parameters = new Properties(); 081 parameters.put(KFSConstants.DISPATCH_REQUEST_PARAMETER, CabConstants.Actions.START); 082 if (glEntry.getReferenceFinancialDocumentNumber() != null) { 083 parameters.put(CabPropertyConstants.PurchasingAccountsPayableDocument.PURCHASE_ORDER_IDENTIFIER, glEntry.getReferenceFinancialDocumentNumber()); 084 } 085 086 String href = UrlFactory.parameterizeUrl(CabConstants.CB_INVOICE_LINE_ACTION_URL, parameters); 087 List<HtmlData> anchorHtmlDataList = new ArrayList<HtmlData>(); 088 AnchorHtmlData anchorHtmlData = new AnchorHtmlData(href, CabConstants.Actions.START, CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equalsIgnoreCase(glEntry.getActivityStatusCode()) ? CabConstants.Actions.VIEW : CabConstants.Actions.PROCESS); 089 anchorHtmlData.setTarget(KFSConstants.NEW_WINDOW_URL_TARGET); 090 anchorHtmlDataList.add(anchorHtmlData); 091 return anchorHtmlDataList; 092 } 093 094 /** 095 * @see org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl#getSearchResults(java.util.Map) 096 */ 097 @Override 098 public List<? extends BusinessObject> getSearchResults(Map<String, String> fieldValues) { 099 setBackLocation((String) fieldValues.get(KFSConstants.BACK_LOCATION)); 100 setDocFormKey((String) fieldValues.get(KFSConstants.DOC_FORM_KEY)); 101 102 // purapDocumentIdentifier should query PurchasingAccountsPayableDocument 103 String purapDocumentIdentifier = getSelectedField(fieldValues, CabPropertyConstants.PurchasingAccountsPayableProcessingReport.PURAP_DOCUMENT_IDENTIFIER); 104 105 // Get the user selects 'Y' for "processed by CAMs". We will search for all status GL lines. This is because of the partial 106 // submitted GL lines when GL is 'N'(new) or 'M'(modified), partial GL lines could submit to CAMs. we should include these 107 // lines into the search result. 108 String active = getSelectedField(fieldValues, CabPropertyConstants.GeneralLedgerEntry.ACTIVITY_STATUS_CODE); 109 if (KFSConstants.ACTIVE_INDICATOR.equalsIgnoreCase(active)) { 110 fieldValues.remove(CabPropertyConstants.GeneralLedgerEntry.ACTIVITY_STATUS_CODE); 111 } 112 // search for GeneralLedgerEntry BO. 113 Iterator searchResultIterator = purApReportService.findGeneralLedgers(fieldValues); 114 // create PurchasingAccountsPayableProcessingReport search result collection. 115 List<PurchasingAccountsPayableProcessingReport> purApReportList = buildGlEntrySearchResultCollection(searchResultIterator, active); 116 117 // purapDocumentIdentifier is the attribute in PurchasingAccountsPayableDocument. We need to generate a new lookup for that 118 // BO, then join search results with the generalLedgerCollection to get the correct search result collection. 119 if (StringUtils.isNotBlank(purapDocumentIdentifier)) { 120 // construct the lookup criteria for PurchasingAccountsPayableDocument from user input 121 Map<String, String> purapDocumentLookupFields = getPurApDocumentLookupFields(fieldValues, purapDocumentIdentifier); 122 123 Collection purApDocs = purApReportService.findPurchasingAccountsPayableDocuments(purapDocumentLookupFields); 124 125 Map<String, Integer> purApDocNumberMap = buildDocumentNumberMap(purApDocs); 126 127 purApReportList = updatePurApReportListByPurApDocs(purApReportList, purApDocNumberMap); 128 } 129 else { 130 purApReportList = updateResultList(purApReportList); 131 } 132 133 134 return buildSearchResultList(purApReportList); 135 } 136 137 /** 138 * Get PurapDocumentIdentifier from PurchasingAccountsPayableDocument and add it to the search result. 139 * 140 * @param purApReportCollection 141 */ 142 private List<PurchasingAccountsPayableProcessingReport> updateResultList(List<PurchasingAccountsPayableProcessingReport> purApReportList) { 143 List<PurchasingAccountsPayableProcessingReport> newResultList = new ArrayList<PurchasingAccountsPayableProcessingReport>(); 144 BusinessObjectService boService = this.getBusinessObjectService(); 145 Map pKeys = new HashMap<String, String>(); 146 147 for (PurchasingAccountsPayableProcessingReport report : purApReportList) { 148 pKeys.put(CabPropertyConstants.PurchasingAccountsPayableDocument.DOCUMENT_NUMBER, report.getDocumentNumber()); 149 PurchasingAccountsPayableDocument purApDocument = (PurchasingAccountsPayableDocument) boService.findByPrimaryKey(PurchasingAccountsPayableDocument.class, pKeys); 150 if (ObjectUtils.isNotNull(purApDocument)) { 151 report.setPurapDocumentIdentifier(purApDocument.getPurapDocumentIdentifier()); 152 newResultList.add(report); 153 } 154 pKeys.clear(); 155 } 156 return newResultList; 157 } 158 159 /** 160 * Build the search result list. 161 * 162 * @param purApReportCollection 163 * @return 164 */ 165 private List<? extends BusinessObject> buildSearchResultList(List purApReportList) { 166 Integer searchResultsLimit = LookupUtils.getSearchResultsLimit(GeneralLedgerEntry.class); 167 Long matchingResultsCount = Long.valueOf(purApReportList.size()); 168 if (matchingResultsCount.intValue() <= searchResultsLimit.intValue()) { 169 matchingResultsCount = Long.valueOf(0); 170 } 171 return new CollectionIncomplete(purApReportList, matchingResultsCount); 172 } 173 174 175 /** 176 * Build a HashMap for documentNumbers from the PurchasingAccountsPayableDocument search results 177 * 178 * @param purApDocs 179 * @return 180 */ 181 private Map<String, Integer> buildDocumentNumberMap(Collection purApDocs) { 182 Map<String, Integer> purApDocNumbers = new HashMap<String, Integer>(); 183 for (Iterator iterator = purApDocs.iterator(); iterator.hasNext();) { 184 PurchasingAccountsPayableDocument purApdoc = (PurchasingAccountsPayableDocument) iterator.next(); 185 purApDocNumbers.put(purApdoc.getDocumentNumber(), purApdoc.getPurapDocumentIdentifier()); 186 } 187 return purApDocNumbers; 188 } 189 190 /** 191 * Build lookup fields for PurchasingAccountsPayableDocument lookup. 192 * 193 * @param fieldValues 194 * @param purapDocumentIdentifier 195 * @return 196 */ 197 private Map<String, String> getPurApDocumentLookupFields(Map<String, String> fieldValues, String purapDocumentIdentifier) { 198 Map<String, String> purapDocumentLookupFields = new HashMap<String, String>(); 199 purapDocumentLookupFields.put(CabPropertyConstants.PurchasingAccountsPayableDocument.PURAP_DOCUMENT_IDENTIFIER, purapDocumentIdentifier); 200 purapDocumentLookupFields.put(CabPropertyConstants.PurchasingAccountsPayableDocument.DOCUMENT_NUMBER, (String) fieldValues.get(CabPropertyConstants.GeneralLedgerEntry.DOCUMENT_NUMBER)); 201 purapDocumentLookupFields.put(CabPropertyConstants.PurchasingAccountsPayableDocument.PURCHASE_ORDER_IDENTIFIER, (String) fieldValues.get(CabPropertyConstants.GeneralLedgerEntry.REFERENCE_FINANCIAL_DOCUMENT_NUMBER)); 202 return purapDocumentLookupFields; 203 } 204 205 /** 206 * Join PurapReportCollection and PurApDocsCollection by documentNumber and remove from PurapReportCollection for mismatch. 207 * 208 * @param purApReportCollection 209 * @param purApDocNumbers 210 */ 211 private List<PurchasingAccountsPayableProcessingReport> updatePurApReportListByPurApDocs(List<PurchasingAccountsPayableProcessingReport> purApReportList, Map<String, Integer> purApDocNumberMap) { 212 List<PurchasingAccountsPayableProcessingReport> newReportsList = new ArrayList<PurchasingAccountsPayableProcessingReport>(); 213 214 for (PurchasingAccountsPayableProcessingReport report : purApReportList) { 215 if (purApDocNumberMap.containsKey(report.getDocumentNumber())) { 216 report.setPurapDocumentIdentifier((Integer) purApDocNumberMap.get(report.getDocumentNumber())); 217 newReportsList.add(report); 218 } 219 } 220 // remove from report collection 221 return newReportsList; 222 } 223 224 /** 225 * Build search result collection as PurchasingAccountsPayableProcessingReport collection. 226 * 227 * @param searchResultIterator 228 * @return 229 */ 230 private List<PurchasingAccountsPayableProcessingReport> buildGlEntrySearchResultCollection(Iterator searchResultIterator, String activeSelection) { 231 List<PurchasingAccountsPayableProcessingReport> purApReportList = new ArrayList(); 232 233 while (searchResultIterator.hasNext()) { 234 Object glEntry = searchResultIterator.next(); 235 236 if (glEntry.getClass().isArray()) { 237 int i = 0; 238 Object[] columnValues = (Object[]) glEntry; 239 240 PurchasingAccountsPayableProcessingReport newReport = new PurchasingAccountsPayableProcessingReport(); 241 242 newReport.setUniversityFiscalYear(new Integer(columnValues[i++].toString())); 243 newReport.setUniversityFiscalPeriodCode(columnValues[i++].toString()); 244 newReport.setChartOfAccountsCode(columnValues[i++].toString()); 245 newReport.setAccountNumber(columnValues[i++].toString()); 246 newReport.setFinancialObjectCode(columnValues[i++].toString()); 247 newReport.setFinancialDocumentTypeCode(columnValues[i++].toString()); 248 newReport.setDocumentNumber(columnValues[i++].toString()); 249 newReport.setTransactionDebitCreditCode(columnValues[i] == null ? null : columnValues[i].toString()); 250 i++; 251 newReport.setTransactionLedgerEntryAmount(columnValues[i] == null ? null : new KualiDecimal(columnValues[i].toString())); 252 i++; 253 newReport.setReferenceFinancialDocumentNumber(columnValues[i] == null ? null : columnValues[i].toString()); 254 i++; 255 newReport.setTransactionDate(columnValues[i] == null ? null : getDate(columnValues[i])); 256 i++; 257 newReport.setTransactionLedgerSubmitAmount(columnValues[i] == null ? null : new KualiDecimal(columnValues[i].toString())); 258 i++; 259 newReport.setActivityStatusCode(columnValues[i] == null ? null : columnValues[i].toString()); 260 261 if (!excludeFromSearchResults(newReport, activeSelection)) { 262 if (newReport.getActivityStatusCode() != null && newReport.isActive()) { 263 // adjust amount if the activity_status_code is 'N' or 'M' 264 if (newReport.getTransactionLedgerEntryAmount() != null) { 265 setReportAmount(activeSelection, newReport); 266 } 267 } 268 else { 269 // set report amount by transactional Amount 270 newReport.setReportAmount(newReport.getAmount()); 271 } 272 purApReportList.add(newReport); 273 } 274 } 275 } 276 return purApReportList; 277 } 278 279 /** 280 * Get the Date instance. Why we need this? Looks OJB returns different type of instance when connect to MySql and Oracle: 281 * Oracle returns Date instance while MySql returns TimeStamp instance. 282 * 283 * @param obj 284 * @return 285 */ 286 private Date getDate(Object obj) { 287 if (obj instanceof Date) { 288 return (Date) obj; 289 } 290 else if (obj instanceof Timestamp) { 291 Timestamp tsp = (Timestamp) obj; 292 return new Date(tsp.getTime()); 293 } 294 else { 295 return null; 296 } 297 } 298 299 /** 300 * To decide if the the given newReport should be added to the search result collection. 301 * 302 * @param newReport 303 * @param activeSelection 304 * @return 305 */ 306 private boolean excludeFromSearchResults(PurchasingAccountsPayableProcessingReport newReport, String activeSelection) { 307 // If the user selects processed by CAMs, we should exclude the GL lines which have no submit amount as the search result 308 if ((KFSConstants.ACTIVE_INDICATOR.equalsIgnoreCase(activeSelection) && (newReport.getTransactionLedgerSubmitAmount() == null || newReport.getTransactionLedgerSubmitAmount().isZero()))) { 309 return true; 310 } 311 return false; 312 } 313 314 /** 315 * set partial commit report amount 316 * 317 * @param active 318 * @param newReport 319 */ 320 private void setReportAmount(String active, PurchasingAccountsPayableProcessingReport newReport) { 321 if (KFSConstants.ACTIVE_INDICATOR.equalsIgnoreCase(active)) { 322 // Processed in CAMS: set report amount as submitted amount 323 newReport.setReportAmount(newReport.getTransactionLedgerSubmitAmount()); 324 } 325 else if ((KFSConstants.NON_ACTIVE_INDICATOR.equalsIgnoreCase(active))) { 326 // Not Processed in CAMS: set report amount by transactionLedgerEntryAmount excluding submitted amount 327 KualiDecimal reportAmount = newReport.getAmount(); 328 if (reportAmount != null && newReport.getTransactionLedgerSubmitAmount() != null) { 329 newReport.setReportAmount(reportAmount.subtract(newReport.getTransactionLedgerSubmitAmount())); 330 } 331 else { 332 newReport.setReportAmount(reportAmount); 333 } 334 } 335 else { 336 // both processed/non processed: set report amount by transactional amount 337 newReport.setReportAmount(newReport.getAmount()); 338 } 339 } 340 341 342 /** 343 * Return and remove the selected field from the user input. 344 * 345 * @param fieldValues 346 * @param fieldName 347 * @return 348 */ 349 private String getSelectedField(Map fieldValues, String fieldName) { 350 String fieldValue = null; 351 352 if (fieldValues.containsKey(fieldName)) { 353 fieldValue = (String) fieldValues.get(fieldName); 354 } 355 return fieldValue == null ? "" : fieldValue; 356 } 357 358 /** 359 * Gets the purApReportService attribute. 360 * 361 * @return Returns the purApReportService. 362 */ 363 public PurchasingAccountsPayableReportService getPurApReportService() { 364 return purApReportService; 365 } 366 367 368 /** 369 * Sets the purApReportService attribute value. 370 * 371 * @param purApReportService The purApReportService to set. 372 */ 373 public void setPurApReportService(PurchasingAccountsPayableReportService purApReportService) { 374 this.purApReportService = purApReportService; 375 } 376 377 public BusinessObjectService getBusinessObjectService() { 378 return SpringContext.getBean(BusinessObjectService.class); 379 } 380 }