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 }