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    }