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.batch.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.sql.Timestamp;
020    import java.text.ParseException;
021    import java.text.SimpleDateFormat;
022    import java.util.ArrayList;
023    import java.util.Calendar;
024    import java.util.Collection;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.LinkedHashMap;
028    import java.util.List;
029    import java.util.Map;
030    
031    import org.apache.log4j.Logger;
032    import org.kuali.kfs.gl.businessobject.Entry;
033    import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
034    import org.kuali.kfs.integration.purap.CapitalAssetSystem;
035    import org.kuali.kfs.integration.purap.ItemCapitalAsset;
036    import org.kuali.kfs.module.cab.CabConstants;
037    import org.kuali.kfs.module.cab.CabPropertyConstants;
038    import org.kuali.kfs.module.cab.batch.ExtractProcessLog;
039    import org.kuali.kfs.module.cab.batch.PreAssetTaggingStep;
040    import org.kuali.kfs.module.cab.batch.dataaccess.ExtractDao;
041    import org.kuali.kfs.module.cab.batch.dataaccess.PurchasingAccountsPayableItemAssetDao;
042    import org.kuali.kfs.module.cab.batch.service.BatchExtractService;
043    import org.kuali.kfs.module.cab.batch.service.ReconciliationService;
044    import org.kuali.kfs.module.cab.businessobject.BatchParameters;
045    import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry;
046    import org.kuali.kfs.module.cab.businessobject.GlAccountLineGroup;
047    import org.kuali.kfs.module.cab.businessobject.Pretag;
048    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableActionHistory;
049    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
050    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
051    import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
052    import org.kuali.kfs.module.cab.document.service.PurApInfoService;
053    import org.kuali.kfs.module.cab.document.service.PurApLineService;
054    import org.kuali.kfs.module.cam.CamsConstants;
055    import org.kuali.kfs.module.cam.CamsPropertyConstants;
056    import org.kuali.kfs.module.cam.businessobject.Asset;
057    import org.kuali.kfs.module.cam.businessobject.AssetGlobal;
058    import org.kuali.kfs.module.cam.document.service.AssetService;
059    import org.kuali.kfs.module.cam.util.KualiDecimalUtils;
060    import org.kuali.kfs.module.purap.PurapConstants;
061    import org.kuali.kfs.module.purap.businessobject.CreditMemoAccountRevision;
062    import org.kuali.kfs.module.purap.businessobject.CreditMemoItem;
063    import org.kuali.kfs.module.purap.businessobject.PaymentRequestAccountRevision;
064    import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem;
065    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase;
066    import org.kuali.kfs.module.purap.businessobject.PurApItem;
067    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderAccount;
068    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
069    import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
070    import org.kuali.kfs.module.purap.document.AccountsPayableDocumentBase;
071    import org.kuali.kfs.module.purap.document.PaymentRequestDocument;
072    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
073    import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument;
074    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
075    import org.kuali.kfs.sys.KFSConstants;
076    import org.kuali.kfs.sys.context.SpringContext;
077    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
078    import org.kuali.rice.kns.bo.Parameter;
079    import org.kuali.rice.kns.service.BusinessObjectService;
080    import org.kuali.rice.kns.service.DateTimeService;
081    import org.kuali.rice.kns.service.ParameterService;
082    import org.kuali.rice.kns.util.DateUtils;
083    import org.kuali.rice.kns.util.KualiDecimal;
084    import org.kuali.rice.kns.util.ObjectUtils;
085    import org.springframework.transaction.annotation.Transactional;
086    
087    /**
088     * This class provides default implementation of {@link BatchExtractService}
089     */
090    @Transactional
091    public class BatchExtractServiceImpl implements BatchExtractService {
092    
093        protected static final Logger LOG = Logger.getLogger(BatchExtractServiceImpl.class);
094        protected BusinessObjectService businessObjectService;
095        protected ExtractDao extractDao;
096        protected DateTimeService dateTimeService;
097        protected ParameterService parameterService;
098        protected PurApLineService purApLineService;
099        protected PurApInfoService purApInfoService;
100        protected PurchasingAccountsPayableItemAssetDao purchasingAccountsPayableItemAssetDao;
101    
102        public void allocateAdditionalCharges(HashSet<PurchasingAccountsPayableDocument> purApDocuments) {
103            List<PurchasingAccountsPayableActionHistory> actionsTakenHistory = new ArrayList<PurchasingAccountsPayableActionHistory>();
104            List<PurchasingAccountsPayableDocument> candidateDocs = new ArrayList<PurchasingAccountsPayableDocument>();
105            List<PurchasingAccountsPayableItemAsset> allocateTargetLines = null;
106            List<PurchasingAccountsPayableItemAsset> initialItems = new ArrayList<PurchasingAccountsPayableItemAsset>();
107            boolean documentUpdated = false;
108    
109            for (PurchasingAccountsPayableDocument purApDoc : purApDocuments) {
110                documentUpdated = false;
111                // Refresh to get the referenced GLEntry BO. This is required to call purApLineService.processAllocate().
112                Map<String, String> primaryKeys = new HashMap<String, String>();
113                primaryKeys.put(CabPropertyConstants.PurchasingAccountsPayableDocument.DOCUMENT_NUMBER, purApDoc.getDocumentNumber());
114                // check if doc is already in CAB
115                PurchasingAccountsPayableDocument cabPurapDoc = (PurchasingAccountsPayableDocument) businessObjectService.findByPrimaryKey(PurchasingAccountsPayableDocument.class, primaryKeys);
116    
117                candidateDocs.add(cabPurapDoc);
118                // keep the original list of items for iteration since purApLineService.processAllocate() may remove the line item when
119                // it's additional charges line.
120                initialItems.addAll(cabPurapDoc.getPurchasingAccountsPayableItemAssets());
121    
122                // set additional charge indicator for each line item from PurAp. we need to set them before hand for use in
123                // purApLineService.getAllocateTargetLines(), in which method to select line items as the target allocating lines.
124                for (PurchasingAccountsPayableItemAsset ititialItem : initialItems) {
125                    purApInfoService.setAccountsPayableItemsFromPurAp(ititialItem, cabPurapDoc.getDocumentTypeCode());
126                }
127    
128                // allocate additional charge lines if it's active line
129                for (PurchasingAccountsPayableItemAsset allocateSourceLine : initialItems) {
130                    if (allocateSourceLine.isAdditionalChargeNonTradeInIndicator() && allocateSourceLine.isActive()) {
131                        // get the allocate additional charge target lines.
132                        allocateTargetLines = purApLineService.getAllocateTargetLines(allocateSourceLine, candidateDocs);
133                        if (allocateTargetLines != null && !allocateTargetLines.isEmpty()) {
134                            // setup the bidirectional relationship between objects.
135                            setupObjectRelationship(candidateDocs);
136                            purApLineService.processAllocate(allocateSourceLine, allocateTargetLines, actionsTakenHistory, candidateDocs, true);
137                            documentUpdated = true;
138                        }
139                    }
140                }
141    
142                if (documentUpdated) {
143                    businessObjectService.save(cabPurapDoc);
144                }
145                candidateDocs.clear();
146                initialItems.clear();
147            }
148        }
149    
150        /**
151         * Setup relationship from account to item and item to doc. In this way, we keep all working objects in the same view as form.
152         * 
153         * @param purApDocs
154         */
155        protected void setupObjectRelationship(List<PurchasingAccountsPayableDocument> purApDocs) {
156            for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
157                for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
158                    item.setPurchasingAccountsPayableDocument(purApDoc);
159                    for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
160                        account.setPurchasingAccountsPayableItemAsset(item);
161                    }
162                }
163            }
164        }
165    
166        /**
167         * Creates a batch parameters object reading values from configured system parameters for CAB Extract
168         * 
169         * @return BatchParameters
170         */
171        protected BatchParameters createCabBatchParameters() {
172            BatchParameters parameters = new BatchParameters();
173            parameters.setLastRunTime(getCabLastRunTimestamp());
174            parameters.setIncludedFinancialBalanceTypeCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.BALANCE_TYPES));
175            parameters.setIncludedFinancialObjectSubTypeCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.OBJECT_SUB_TYPES));
176            parameters.setExcludedChartCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.CHARTS));
177            parameters.setExcludedDocTypeCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.DOCUMENT_TYPES));
178            parameters.setExcludedFiscalPeriods(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.FISCAL_PERIODS));
179            parameters.setExcludedSubFundCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.SUB_FUND_GROUPS));
180            return parameters;
181        }
182    
183        /**
184         * Creates a batch parameters object reading values from configured system parameters for Pre Tagging Extract
185         * 
186         * @return BatchParameters
187         */
188        protected BatchParameters createPreTagBatchParameters() {
189            BatchParameters parameters = new BatchParameters();
190            parameters.setLastRunDate(getPreTagLastRunDate());
191            parameters.setIncludedFinancialObjectSubTypeCodes(parameterService.getParameterValues(PreAssetTaggingStep.class, CabConstants.Parameters.OBJECT_SUB_TYPES));
192            parameters.setExcludedChartCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.CHARTS));
193            parameters.setExcludedSubFundCodes(parameterService.getParameterValues(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.SUB_FUND_GROUPS));
194            parameters.setCapitalizationLimitAmount(new BigDecimal(parameterService.getParameterValue(AssetGlobal.class, CamsConstants.Parameters.CAPITALIZATION_LIMIT_AMOUNT)));
195            return parameters;
196        }
197    
198        /**
199         * Retrieves a credit memo document for a specific document number
200         * 
201         * @param entry GL Line
202         * @return CreditMemoDocument
203         */
204        protected VendorCreditMemoDocument findCreditMemoDocument(Entry entry) {
205            VendorCreditMemoDocument creditMemoDocument = null;
206            Map<String, String> keys = new LinkedHashMap<String, String>();
207            keys.put(CabPropertyConstants.DOCUMENT_NUMBER, entry.getDocumentNumber());
208            Collection<VendorCreditMemoDocument> matchingCms = businessObjectService.findMatching(VendorCreditMemoDocument.class, keys);
209            if (matchingCms != null && matchingCms.size() == 1) {
210                creditMemoDocument = matchingCms.iterator().next();
211            }
212            return creditMemoDocument;
213        }
214    
215        /**
216         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#findElgibleGLEntries()
217         */
218        public Collection<Entry> findElgibleGLEntries(ExtractProcessLog processLog) {
219            BatchParameters parameters = createCabBatchParameters();
220            processLog.setLastExtractTime(parameters.getLastRunTime());
221            return getExtractDao().findMatchingGLEntries(parameters);
222        }
223    
224        /**
225         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#findPreTaggablePOAccounts()
226         */
227        public Collection<PurchaseOrderAccount> findPreTaggablePOAccounts() {
228            BatchParameters parameters = createPreTagBatchParameters();
229            return getExtractDao().findPreTaggablePOAccounts(parameters);
230        }
231    
232    
233        /**
234         * Retrieves a payment request document for a specific document number
235         * 
236         * @param entry GL Line
237         * @return PaymentRequestDocument
238         */
239        protected PaymentRequestDocument findPaymentRequestDocument(Entry entry) {
240            PaymentRequestDocument paymentRequestDocument = null;
241            Map<String, String> keys = new LinkedHashMap<String, String>();
242            keys.put(CabPropertyConstants.DOCUMENT_NUMBER, entry.getDocumentNumber());
243            Collection<PaymentRequestDocument> matchingPreqs = businessObjectService.findMatching(PaymentRequestDocument.class, keys);
244            if (matchingPreqs != null && matchingPreqs.size() == 1) {
245                paymentRequestDocument = matchingPreqs.iterator().next();
246            }
247            return paymentRequestDocument;
248        }
249    
250    
251        /**
252         * Computes the last run time stamp, if null then it gives yesterday
253         * 
254         * @return Last run time stamp
255         */
256        protected Timestamp getCabLastRunTimestamp() {
257            Timestamp lastRunTime;
258            String lastRunTS = parameterService.getParameterValue(KfsParameterConstants.CAPITAL_ASSET_BUILDER_BATCH.class, CabConstants.Parameters.LAST_EXTRACT_TIME);
259            java.util.Date yesterday = DateUtils.add(dateTimeService.getCurrentDate(), Calendar.DAY_OF_MONTH, -1);
260            try {
261                lastRunTime = lastRunTS == null ? new Timestamp(yesterday.getTime()) : new Timestamp(DateUtils.parseDate(lastRunTS, new String[] { CabConstants.DateFormats.MONTH_DAY_YEAR + " " + CabConstants.DateFormats.MILITARY_TIME }).getTime());
262            }
263            catch (ParseException e) {
264                throw new RuntimeException(e);
265            }
266            return lastRunTime;
267        }
268    
269        /**
270         * Gets the last pre tag extract run date from system parameter
271         * 
272         * @return
273         */
274        protected java.sql.Date getPreTagLastRunDate() {
275            java.sql.Date lastRunDt;
276            String lastRunTS = parameterService.getParameterValue(PreAssetTaggingStep.class, CabConstants.Parameters.LAST_EXTRACT_DATE);
277            java.util.Date yesterday = DateUtils.add(dateTimeService.getCurrentDate(), Calendar.DAY_OF_MONTH, -1);
278            try {
279                lastRunDt = lastRunTS == null ? new java.sql.Date(yesterday.getTime()) : new java.sql.Date(DateUtils.parseDate(lastRunTS, new String[] { CabConstants.DateFormats.MONTH_DAY_YEAR }).getTime());
280            }
281            catch (ParseException e) {
282                throw new RuntimeException(e);
283            }
284            return lastRunDt;
285        }
286    
287        /**
288         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#saveFPLines(java.util.List)
289         */
290        public void saveFPLines(List<Entry> fpLines, ExtractProcessLog processLog) {
291            for (Entry fpLine : fpLines) {
292                // If entry is not duplicate, non-null and non-zero, then insert into CAB
293                ReconciliationService reconciliationService = SpringContext.getBean(ReconciliationService.class);
294                if (fpLine.getTransactionLedgerEntryAmount() == null || fpLine.getTransactionLedgerEntryAmount().isZero()) {
295                    // amount is zero or null
296                    processLog.addIgnoredGLEntry(fpLine);
297                }
298                else if (reconciliationService.isDuplicateEntry(fpLine)) {
299                    // GL is duplicate
300                    processLog.addDuplicateGLEntry(fpLine);
301                }
302                else {
303                    GeneralLedgerEntry glEntry = new GeneralLedgerEntry(fpLine);
304                    businessObjectService.save(glEntry);
305                }
306            }
307        }
308    
309        /**
310         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#savePOLines(java.util.List)
311         */
312        public HashSet<PurchasingAccountsPayableDocument> savePOLines(List<Entry> poLines, ExtractProcessLog processLog) {
313            HashSet<PurchasingAccountsPayableDocument> purApDocuments = new HashSet<PurchasingAccountsPayableDocument>();
314            ReconciliationService reconciliationService = SpringContext.getBean(ReconciliationService.class);
315            // This is a list of pending GL entries created after last GL process and Cab Batch extract
316            // PurAp Account Line history comes from PURAP module
317            Collection<PurApAccountingLineBase> purapAcctLines = findPurapAccountRevisions();
318            // Pass the records to reconciliation service method
319            reconciliationService.reconcile(poLines, purapAcctLines);
320    
321            // for each valid GL entry there is a collection of valid PO Doc and Account Lines
322            Collection<GlAccountLineGroup> matchedGroups = reconciliationService.getMatchedGroups();
323    
324            // Keep track of unique item lines
325            HashMap<String, PurchasingAccountsPayableItemAsset> assetItems = new HashMap<String, PurchasingAccountsPayableItemAsset>();
326    
327            // Keep track of unique account lines
328            HashMap<String, PurchasingAccountsPayableLineAssetAccount> assetAcctLines = new HashMap<String, PurchasingAccountsPayableLineAssetAccount>();
329    
330            // Keep track of asset lock
331            HashMap<String, Object> assetLockMap = new HashMap<String, Object>();
332            // Keep track of purchaseOrderDocument
333            HashMap<Integer, PurchaseOrderDocument> poDocMap = new HashMap<Integer, PurchaseOrderDocument>();
334    
335            for (GlAccountLineGroup group : matchedGroups) {
336                Entry entry = group.getTargetEntry();
337                GeneralLedgerEntry generalLedgerEntry = new GeneralLedgerEntry(entry);
338                GeneralLedgerEntry positiveEntry = null;
339                GeneralLedgerEntry negativeEntry = null;
340                KualiDecimal transactionLedgerEntryAmount = generalLedgerEntry.getTransactionLedgerEntryAmount();
341                
342                boolean hasFORevision = isFinancialOfficerRevised(group.getMatchedPurApAcctLines());
343                // generally non-zero transaction ledger amount should be create a single GL entry, but if it has FO revised, 
344                // create the set of positive and negative entries with zero transaction amounts
345                if (transactionLedgerEntryAmount != null && transactionLedgerEntryAmount.isNonZero() &&  !hasFORevision) {
346                    businessObjectService.save(generalLedgerEntry);
347                }
348                else {
349                    positiveEntry = createPositiveGlEntry(entry);
350                    businessObjectService.save(positiveEntry);
351                    negativeEntry = createNegativeGlEntry(entry);
352                    businessObjectService.save(negativeEntry);
353                }
354    
355                boolean newApDoc = true;
356                PurchasingAccountsPayableDocument cabPurapDoc = findPurchasingAccountsPayableDocument(entry);
357                // if document is found already, update the active flag
358                if (ObjectUtils.isNull(cabPurapDoc)) {
359                    cabPurapDoc = createPurchasingAccountsPayableDocument(entry);
360                }
361                else {
362                    newApDoc = false;
363                }
364                if (cabPurapDoc != null) {
365    
366    
367                    List<PurApAccountingLineBase> matchedPurApAcctLines = group.getMatchedPurApAcctLines();
368    
369                    for (PurApAccountingLineBase purApAccountingLine : matchedPurApAcctLines) {
370                        PurApItem purapItem = purApAccountingLine.getPurapItem();
371                        PurchasingAccountsPayableItemAsset itemAsset = findMatchingPurapAssetItem(cabPurapDoc, purapItem);
372                        String itemAssetKey = cabPurapDoc.getDocumentNumber() + "-" + purapItem.getItemIdentifier();
373    
374                        // if new item, create and add to the list
375                        if (itemAsset == null && (itemAsset = assetItems.get(itemAssetKey)) == null) {
376                            itemAsset = createPurchasingAccountsPayableItemAsset(cabPurapDoc, purapItem);
377                            cabPurapDoc.getPurchasingAccountsPayableItemAssets().add(itemAsset);
378                            assetItems.put(itemAssetKey, itemAsset);
379                        }
380                        PurchasingAccountsPayableLineAssetAccount assetAccount = null;
381                        String acctLineKey = cabPurapDoc.getDocumentNumber() + "-" + itemAsset.getAccountsPayableLineItemIdentifier() + "-" + itemAsset.getCapitalAssetBuilderLineNumber() + "-" + generalLedgerEntry.getGeneralLedgerAccountIdentifier();
382    
383    
384                        if ((assetAccount = assetAcctLines.get(acctLineKey)) == null && transactionLedgerEntryAmount.isNonZero() && !hasFORevision) {
385                            // if new unique account line within GL, then create a new account line
386                            assetAccount = createPurchasingAccountsPayableLineAssetAccount(generalLedgerEntry, cabPurapDoc, purApAccountingLine, itemAsset);
387                            assetAcctLines.put(acctLineKey, assetAccount);
388                            itemAsset.getPurchasingAccountsPayableLineAssetAccounts().add(assetAccount);
389                        }
390                        else if (transactionLedgerEntryAmount.isZero() || hasFORevision) {
391                            GeneralLedgerEntry currentEntry = null;
392                            // if amount is zero, means canceled doc, then create a copy and retain the account line
393                            KualiDecimal purapAmount = purApAccountingLine.getAmount();
394                            if (CabConstants.CM.equals(entry.getFinancialDocumentTypeCode())) {
395                                purapAmount = purapAmount.negated();
396                            }
397                            if (purapAmount.isPositive()) {
398                                currentEntry = positiveEntry;
399                            }
400                            else {
401                                currentEntry = negativeEntry;
402                            }
403                            currentEntry.setTransactionLedgerEntryAmount(currentEntry.getTransactionLedgerEntryAmount().add(purapAmount.abs()));
404                            assetAccount = createPurchasingAccountsPayableLineAssetAccount(currentEntry, cabPurapDoc, purApAccountingLine, itemAsset);
405                            itemAsset.getPurchasingAccountsPayableLineAssetAccounts().add(assetAccount);
406                        }
407                        else if ((assetAccount = assetAcctLines.get(acctLineKey)) != null) {
408                            // if account line key matches within same GL Entry, combine the amount
409                            assetAccount.setItemAccountTotalAmount(assetAccount.getItemAccountTotalAmount().add(purApAccountingLine.getAmount()));
410                        }
411    
412                        // Add to the asset lock table if purap has asset number information
413                        addAssetLocks(assetLockMap, cabPurapDoc, purapItem, itemAsset.getAccountsPayableLineItemIdentifier(), poDocMap);
414                    }
415                    businessObjectService.save(cabPurapDoc);
416    
417                    // update negative and positive GL entry once again
418                    if (positiveEntry != null && negativeEntry != null) {
419                        businessObjectService.save(positiveEntry);
420                        businessObjectService.save(negativeEntry);
421                    }
422    
423                    // Add to the doc collection which will be used for additional charge allocating. This will be the next step during
424                    // batch.
425                    if (newApDoc) {
426                        purApDocuments.add(cabPurapDoc);
427                    }
428    
429    
430                }
431                else {
432                    LOG.error("Could not create a valid PurchasingAccountsPayableDocument object for document number " + entry.getDocumentNumber());
433                }
434            }
435            updateProcessLog(processLog, reconciliationService);
436            return purApDocuments;
437        }
438    
439        /**
440         * Base on the PurApAccountingLine to determine if there were any FinancialOfficer revisions
441         * 
442         * If there are positive AND negative accounting lines for the same account, it is considered Financial 
443         * Officer Revised 
444         * 
445         * @param matchedPurApAcctLines
446         * @return
447         */
448        private boolean isFinancialOfficerRevised(List<PurApAccountingLineBase> matchedPurApAcctLines) {
449            
450            boolean hasPostive = false;
451            boolean hasNegative = false;
452            
453            for (PurApAccountingLineBase line : matchedPurApAcctLines){
454                // continue iterations unless it has both positive and negative entries
455                if (!hasPostive || !hasNegative){
456                    hasPostive = hasPostive || line.getAmount().isPositive();
457                    hasNegative = hasNegative || line.getAmount().isNegative();
458                }
459            }
460            
461            return hasPostive && hasNegative;
462        }
463    
464        /**
465         * Add asset lock to prohibit CAMS user modify the asset payment line.
466         * 
467         * @param assetLockMap
468         * @param cabPurapDoc
469         * @param purapItem
470         * @param itemAsset
471         * @param poDocMap
472         */
473        protected void addAssetLocks(HashMap<String, Object> assetLockMap, PurchasingAccountsPayableDocument cabPurapDoc, PurApItem purapItem, Integer accountsPaymentItemId, HashMap<Integer, PurchaseOrderDocument> poDocMap) {
474            PurchaseOrderDocument purApdocument = null;
475            if (poDocMap.containsKey(cabPurapDoc.getPurchaseOrderIdentifier())) {
476                purApdocument = poDocMap.get(cabPurapDoc.getPurchaseOrderIdentifier());
477            }
478            else {
479                purApdocument = getPurApInfoService().getCurrentDocumentForPurchaseOrderIdentifier(cabPurapDoc.getPurchaseOrderIdentifier());
480                poDocMap.put(cabPurapDoc.getPurchaseOrderIdentifier(), purApdocument);
481            }
482            String assetLockKey = cabPurapDoc.getDocumentNumber();
483            // Only individual system will lock on item line number. other system will using preq/cm doc nbr as the locking
484            // key
485            String lockingInformation = null;
486            if (PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS.equalsIgnoreCase(purApdocument.getCapitalAssetSystemTypeCode())) {
487                lockingInformation = accountsPaymentItemId.toString();
488                assetLockKey = cabPurapDoc.getDocumentNumber() + "-" + lockingInformation;
489            }
490            // set asset locks if the locks does not exist in HashMap and not in asset lock table either.
491            if (!assetLockMap.containsKey(assetLockKey) && !getCapitalAssetManagementModuleService().isAssetLockedByCurrentDocument(cabPurapDoc.getDocumentNumber(), lockingInformation)) {
492                // the below method need several PurAp service calls which may take long time to run.
493                List capitalAssetNumbers = getAssetNumbersForLocking(purApdocument, purapItem);
494                if (capitalAssetNumbers != null && !capitalAssetNumbers.isEmpty()) {
495                    boolean lockingResult = this.getCapitalAssetManagementModuleService().storeAssetLocks(capitalAssetNumbers, cabPurapDoc.getDocumentNumber(), cabPurapDoc.getDocumentTypeCode(), lockingInformation);
496                    // add into cache
497                    assetLockMap.put(assetLockKey, lockingResult);
498                }
499                else {
500                    // remember the decision...
501                    assetLockMap.put(assetLockKey, false);
502                }
503            }
504        }
505    
506        protected List getAssetNumbersForLocking(PurchaseOrderDocument purApdocument, PurApItem purapItem) {
507            String capitalAssetSystemTypeCode = purApdocument.getCapitalAssetSystemTypeCode();
508            if (!PurapConstants.CapitalAssetSystemStates.MODIFY.equalsIgnoreCase(purApdocument.getCapitalAssetSystemStateCode())) {
509                return null;
510            }
511            return getPurApInfoService().retrieveValidAssetNumberForLocking(purApdocument.getPurapDocumentIdentifier(), purApdocument.getCapitalAssetSystemTypeCode(), purapItem);
512        }
513    
514    
515        protected CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
516            return SpringContext.getBean(CapitalAssetManagementModuleService.class);
517        }
518    
519        /**
520         * Force created entry with zero transaction amount
521         * 
522         * @param entry
523         * @return
524         */
525        protected GeneralLedgerEntry createPositiveGlEntry(Entry entry) {
526            GeneralLedgerEntry copyEntry = new GeneralLedgerEntry(entry);
527            copyEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
528            copyEntry.setTransactionLedgerEntryAmount(KualiDecimal.ZERO);
529            return copyEntry;
530        }
531    
532        /**
533         * Force created entry with zero transaction amount
534         * 
535         * @param entry
536         * @return
537         */
538        protected GeneralLedgerEntry createNegativeGlEntry(Entry entry) {
539            GeneralLedgerEntry copyEntry = new GeneralLedgerEntry(entry);
540            copyEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
541            copyEntry.setTransactionLedgerEntryAmount(KualiDecimal.ZERO);
542            return copyEntry;
543        }
544    
545    
546        /**
547         * Retrieves Payment Request Account History and Credit Memo account history, combines them into a single list
548         * 
549         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#findPurapAccountHistory()
550         */
551        public Collection<PurApAccountingLineBase> findPurapAccountRevisions() {
552            Collection<PurApAccountingLineBase> purapAcctLines = new ArrayList<PurApAccountingLineBase>();
553            Collection<CreditMemoAccountRevision> cmAccountHistory = extractDao.findCreditMemoAccountRevisions(createCabBatchParameters());
554            Collection<PaymentRequestAccountRevision> preqAccountHistory = extractDao.findPaymentRequestAccountRevisions(createCabBatchParameters());
555            if (cmAccountHistory != null) {
556                purapAcctLines.addAll(cmAccountHistory);
557            }
558            if (preqAccountHistory != null) {
559                purapAcctLines.addAll(preqAccountHistory);
560            }
561            return purapAcctLines;
562        }
563    
564        /**
565         * Creates a new instance of PurchasingAccountsPayableLineAssetAccount using values provided from dependent objects
566         * 
567         * @param generalLedgerEntry General Ledger Entry record
568         * @param cabPurapDoc CAB PurAp Document
569         * @param purApAccountingLine PurAp accounting line
570         * @param itemAsset CAB PurAp Item Asset
571         * @return New PurchasingAccountsPayableLineAssetAccount
572         */
573        protected PurchasingAccountsPayableLineAssetAccount createPurchasingAccountsPayableLineAssetAccount(GeneralLedgerEntry generalLedgerEntry, PurchasingAccountsPayableDocument cabPurapDoc, PurApAccountingLineBase purApAccountingLine, PurchasingAccountsPayableItemAsset itemAsset) {
574            PurchasingAccountsPayableLineAssetAccount assetAccount = new PurchasingAccountsPayableLineAssetAccount();
575            assetAccount.setDocumentNumber(cabPurapDoc.getDocumentNumber());
576            assetAccount.setAccountsPayableLineItemIdentifier(itemAsset.getAccountsPayableLineItemIdentifier());
577            assetAccount.setCapitalAssetBuilderLineNumber(itemAsset.getCapitalAssetBuilderLineNumber());
578            assetAccount.setGeneralLedgerAccountIdentifier(generalLedgerEntry.getGeneralLedgerAccountIdentifier());
579            if (CabConstants.CM.equals(generalLedgerEntry.getFinancialDocumentTypeCode())) {
580                assetAccount.setItemAccountTotalAmount(purApAccountingLine.getAmount().negated());
581            }
582            else {
583                assetAccount.setItemAccountTotalAmount(purApAccountingLine.getAmount());
584            }
585            assetAccount.setActivityStatusCode(CabConstants.ActivityStatusCode.NEW);
586            assetAccount.setVersionNumber(0L);
587            return assetAccount;
588        }
589    
590        /**
591         * Updates the entries into process log
592         * 
593         * @param processLog Extract Process Log
594         * @param reconciliationService Reconciliation Service data
595         */
596        protected void updateProcessLog(ExtractProcessLog processLog, ReconciliationService reconciliationService) {
597            processLog.addIgnoredGLEntries(reconciliationService.getIgnoredEntries());
598            processLog.addDuplicateGLEntries(reconciliationService.getDuplicateEntries());
599            Collection<GlAccountLineGroup> misMatchedGroups = reconciliationService.getMisMatchedGroups();
600            for (GlAccountLineGroup glAccountLineGroup : misMatchedGroups) {
601                processLog.addMismatchedGLEntries(glAccountLineGroup.getSourceEntries());
602            }
603        }
604    
605        /**
606         * Finds PurchasingAccountsPayableDocument using document number
607         * 
608         * @param entry GL Entry
609         * @return PurchasingAccountsPayableDocument
610         */
611        protected PurchasingAccountsPayableDocument findPurchasingAccountsPayableDocument(Entry entry) {
612            Map<String, String> primaryKeys = new HashMap<String, String>();
613            primaryKeys.put(CabPropertyConstants.PurchasingAccountsPayableDocument.DOCUMENT_NUMBER, entry.getDocumentNumber());
614            // check if doc is already in CAB
615            PurchasingAccountsPayableDocument cabPurapDoc = (PurchasingAccountsPayableDocument) businessObjectService.findByPrimaryKey(PurchasingAccountsPayableDocument.class, primaryKeys);
616            return cabPurapDoc;
617        }
618    
619        /**
620         * Creates a new PurchasingAccountsPayableItemAsset using Purchasing Accounts payable item
621         * 
622         * @param cabPurapDoc Cab Purap Document
623         * @param apItem Accounts Payable Item
624         * @return PurchasingAccountsPayableItemAsset
625         */
626        protected PurchasingAccountsPayableItemAsset createPurchasingAccountsPayableItemAsset(PurchasingAccountsPayableDocument cabPurapDoc, PurApItem apItem) {
627            PurchasingAccountsPayableItemAsset itemAsset = new PurchasingAccountsPayableItemAsset();
628            itemAsset.setDocumentNumber(cabPurapDoc.getDocumentNumber());
629            itemAsset.setAccountsPayableLineItemIdentifier(apItem.getItemIdentifier());
630            itemAsset.setCapitalAssetBuilderLineNumber(purchasingAccountsPayableItemAssetDao.findMaxCabLineNumber(cabPurapDoc.getDocumentNumber(), apItem.getItemIdentifier()) + 1);
631            itemAsset.setAccountsPayableLineItemDescription(apItem.getItemDescription());
632            itemAsset.setAccountsPayableItemQuantity(apItem.getItemQuantity() == null ? new KualiDecimal(1) : apItem.getItemQuantity());
633            itemAsset.setActivityStatusCode(CabConstants.ActivityStatusCode.NEW);
634            itemAsset.setVersionNumber(0L);
635            return itemAsset;
636        }
637    
638        /**
639         * This method creates PurchasingAccountsPayableDocument from a GL Entry and AP Document
640         * 
641         * @param entry GL Entry
642         * @param apDoc AP Document
643         * @return PurchasingAccountsPayableDocument
644         */
645        protected PurchasingAccountsPayableDocument createPurchasingAccountsPayableDocument(Entry entry) {
646            AccountsPayableDocumentBase apDoc = null;
647            PurchasingAccountsPayableDocument cabPurapDoc = null;
648            // If document is not in CAB, create a new document to save in CAB
649            if (CabConstants.PREQ.equals(entry.getFinancialDocumentTypeCode())) {
650                // find PREQ
651                apDoc = findPaymentRequestDocument(entry);
652            }
653            else if (CabConstants.CM.equals(entry.getFinancialDocumentTypeCode())) {
654                // find CM
655                apDoc = findCreditMemoDocument(entry);
656            }
657            if (apDoc == null) {
658                LOG.error("A valid Purchasing Document (PREQ or CM) could not be found for this document number " + entry.getDocumentNumber());
659            }
660            else {
661                cabPurapDoc = new PurchasingAccountsPayableDocument();
662                cabPurapDoc.setDocumentNumber(entry.getDocumentNumber());
663                cabPurapDoc.setPurapDocumentIdentifier(apDoc.getPurapDocumentIdentifier());
664                cabPurapDoc.setPurchaseOrderIdentifier(apDoc.getPurchaseOrderIdentifier());
665                cabPurapDoc.setDocumentTypeCode(entry.getFinancialDocumentTypeCode());
666                cabPurapDoc.setActivityStatusCode(CabConstants.ActivityStatusCode.NEW);
667                cabPurapDoc.setVersionNumber(0L);
668            }
669            return cabPurapDoc;
670        }
671    
672        /**
673         * Finds out the active CAB Asset Item matching the line from PurAP.
674         * 
675         * @param cabPurapDoc CAB PurAp document
676         * @param apItem AP Item
677         * @return PurchasingAccountsPayableItemAsset
678         */
679        protected PurchasingAccountsPayableItemAsset findMatchingPurapAssetItem(PurchasingAccountsPayableDocument cabPurapDoc, PurApItem apItem) {
680            Map<String, Object> keys = new HashMap<String, Object>();
681            keys.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.DOCUMENT_NUMBER, cabPurapDoc.getDocumentNumber());
682            keys.put(CabPropertyConstants.PurchasingAccountsPayableItemAsset.ACCOUNTS_PAYABLE_LINE_ITEM_IDENTIFIER, apItem.getItemIdentifier());
683            Collection<PurchasingAccountsPayableItemAsset> matchingItems = businessObjectService.findMatching(PurchasingAccountsPayableItemAsset.class, keys);
684            if (matchingItems != null && !matchingItems.isEmpty() && matchingItems.size() == 1) {
685                PurchasingAccountsPayableItemAsset itmAsset = matchingItems.iterator().next();
686                // if still active and never split or submitted to CAMS
687                KualiDecimal cabQty = itmAsset.getAccountsPayableItemQuantity();
688                KualiDecimal purapQty = apItem.getItemQuantity();
689                if (CabConstants.ActivityStatusCode.NEW.equalsIgnoreCase(itmAsset.getActivityStatusCode())) {
690                    return itmAsset;
691                }
692            }
693            return null;
694        }
695    
696        /**
697         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#separatePOLines(java.util.List, java.util.List,
698         *      java.util.Collection)
699         */
700        public void separatePOLines(List<Entry> fpLines, List<Entry> purapLines, Collection<Entry> elgibleGLEntries) {
701            for (Entry entry : elgibleGLEntries) {
702                if (CabConstants.PREQ.equals(entry.getFinancialDocumentTypeCode())) {
703                    purapLines.add(entry);
704                }
705                else if (!CabConstants.CM.equals(entry.getFinancialDocumentTypeCode())) {
706                    fpLines.add(entry);
707                }
708                else if (CabConstants.CM.equals(entry.getFinancialDocumentTypeCode())) {
709                    Map<String, String> fieldValues = new HashMap<String, String>();
710                    fieldValues.put(CabPropertyConstants.GeneralLedgerEntry.DOCUMENT_NUMBER, entry.getDocumentNumber());
711                    // check if vendor credit memo, then include as FP line
712                    Collection<VendorCreditMemoDocument> matchingCreditMemos = businessObjectService.findMatching(VendorCreditMemoDocument.class, fieldValues);
713                    for (VendorCreditMemoDocument creditMemoDocument : matchingCreditMemos) {
714                        if (creditMemoDocument.getPurchaseOrderIdentifier() == null) {
715                            fpLines.add(entry);
716                        }
717                        else {
718                            purapLines.add(entry);
719                        }
720                    }
721                }
722            }
723        }
724    
725    
726        /**
727         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#updateLastExtractTime(java.sql.Timestamp)
728         */
729        public void updateLastExtractTime(Timestamp time) {
730            Map<String, String> primaryKeys = new LinkedHashMap<String, String>();
731            primaryKeys.put(CabPropertyConstants.Parameter.PARAMETER_NAMESPACE_CODE, CabConstants.Parameters.NAMESPACE);
732            primaryKeys.put(CabPropertyConstants.Parameter.PARAMETER_DETAIL_TYPE_CODE, CabConstants.Parameters.DETAIL_TYPE_BATCH);
733            primaryKeys.put(CabPropertyConstants.Parameter.PARAMETER_NAME, CabConstants.Parameters.LAST_EXTRACT_TIME);
734            Parameter parameter = (Parameter) businessObjectService.findByPrimaryKey(Parameter.class, primaryKeys);
735    
736            if (parameter != null) {
737                SimpleDateFormat format = new SimpleDateFormat(CabConstants.DateFormats.MONTH_DAY_YEAR + " " + CabConstants.DateFormats.MILITARY_TIME);
738                parameter.setParameterValue(format.format(time));
739                businessObjectService.save(parameter);
740            }
741        }
742    
743        /**
744         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#savePreTagLines(java.util.Collection)
745         */
746        public void savePreTagLines(Collection<PurchaseOrderAccount> preTaggablePOAccounts) {
747            HashSet<String> savedLines = new HashSet<String>();
748            for (PurchaseOrderAccount purchaseOrderAccount : preTaggablePOAccounts) {
749                purchaseOrderAccount.refresh();
750                PurchaseOrderItem purapItem = purchaseOrderAccount.getPurapItem();
751                PurchaseOrderDocument purchaseOrder = purapItem.getPurchaseOrder();
752                if (ObjectUtils.isNotNull(purchaseOrder)) {
753                    Integer poId = purchaseOrder.getPurapDocumentIdentifier();
754                    Integer itemLineNumber = purapItem.getItemLineNumber();
755                    if (poId != null && itemLineNumber != null) {
756                        Map<String, Object> primaryKeys = new HashMap<String, Object>();
757                        primaryKeys.put(CabPropertyConstants.Pretag.PURCHASE_ORDER_NUMBER, poId);
758                        primaryKeys.put(CabPropertyConstants.Pretag.ITEM_LINE_NUMBER, itemLineNumber);
759                        // check if already in pre-tag table
760                        Pretag pretag = (Pretag) businessObjectService.findByPrimaryKey(Pretag.class, primaryKeys);
761                        if (ObjectUtils.isNull(pretag) && savedLines.add("" + poId + "-" + itemLineNumber)) {
762                            pretag = new Pretag();
763                            pretag.setPurchaseOrderNumber(poId.toString());
764                            pretag.setItemLineNumber(itemLineNumber);
765                            KualiDecimal quantity = purapItem.getItemQuantity();
766                            pretag.setQuantityInvoiced(quantity != null ? quantity : new KualiDecimal(1));
767                            pretag.setVendorName(purchaseOrder.getVendorName());
768                            pretag.setAssetTopsDescription(purapItem.getItemDescription());
769                            pretag.setPretagCreateDate(new java.sql.Date(purchaseOrder.getPurchaseOrderInitialOpenTimestamp().getTime()));
770                            pretag.setChartOfAccountsCode(purchaseOrder.getChartOfAccountsCode());
771                            pretag.setOrganizationCode(purchaseOrder.getOrganizationCode());
772                            pretag.setActive(true);
773                            businessObjectService.save(pretag);
774                        }
775                    }
776                }
777            }
778        }
779    
780        /**
781         * @see org.kuali.kfs.module.cab.batch.service.BatchExtractService#updateLastExtractDate(java.sql.Date)
782         */
783        public void updateLastExtractDate(java.sql.Date dt) {
784            Map<String, String> primaryKeys = new LinkedHashMap<String, String>();
785            primaryKeys.put(CabPropertyConstants.Parameter.PARAMETER_NAMESPACE_CODE, CabConstants.Parameters.NAMESPACE);
786            primaryKeys.put(CabPropertyConstants.Parameter.PARAMETER_DETAIL_TYPE_CODE, CabConstants.Parameters.DETAIL_TYPE_PRE_ASSET_TAGGING_STEP);
787            primaryKeys.put(CabPropertyConstants.Parameter.PARAMETER_NAME, CabConstants.Parameters.LAST_EXTRACT_DATE);
788            Parameter parameter = (Parameter) businessObjectService.findByPrimaryKey(Parameter.class, primaryKeys);
789    
790            if (parameter != null) {
791                SimpleDateFormat format = new SimpleDateFormat(CabConstants.DateFormats.MONTH_DAY_YEAR);
792                parameter.setParameterValue(format.format(dt));
793                businessObjectService.save(parameter);
794            }
795        }
796    
797        /**
798         * Gets the businessObjectService attribute.
799         * 
800         * @return Returns the businessObjectService.
801         */
802        public BusinessObjectService getBusinessObjectService() {
803            return businessObjectService;
804        }
805    
806        /**
807         * Sets the businessObjectService attribute value.
808         * 
809         * @param businessObjectService The businessObjectService to set.
810         */
811        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
812            this.businessObjectService = businessObjectService;
813        }
814    
815        /**
816         * Gets the extractDao attribute.
817         * 
818         * @return Returns the extractDao.
819         */
820        public ExtractDao getExtractDao() {
821            return extractDao;
822        }
823    
824        /**
825         * Sets the extractDao attribute value.
826         * 
827         * @param extractDao The extractDao to set.
828         */
829        public void setExtractDao(ExtractDao extractDao) {
830            this.extractDao = extractDao;
831        }
832    
833        /**
834         * Gets the dateTimeService attribute.
835         * 
836         * @return Returns the dateTimeService.
837         */
838        public DateTimeService getDateTimeService() {
839            return dateTimeService;
840        }
841    
842        /**
843         * Sets the dateTimeService attribute value.
844         * 
845         * @param dateTimeService The dateTimeService to set.
846         */
847        public void setDateTimeService(DateTimeService dateTimeService) {
848            this.dateTimeService = dateTimeService;
849        }
850    
851        /**
852         * Gets the parameterService attribute.
853         * 
854         * @return Returns the parameterService.
855         */
856        public ParameterService getParameterService() {
857            return parameterService;
858        }
859    
860        /**
861         * Sets the parameterService attribute value.
862         * 
863         * @param parameterService The parameterService to set.
864         */
865        public void setParameterService(ParameterService parameterService) {
866            this.parameterService = parameterService;
867        }
868    
869    
870        /**
871         * Gets the purchasingAccountsPayableItemAssetDao attribute.
872         * 
873         * @return Returns the purchasingAccountsPayableItemAssetDao.
874         */
875        public PurchasingAccountsPayableItemAssetDao getPurchasingAccountsPayableItemAssetDao() {
876            return purchasingAccountsPayableItemAssetDao;
877        }
878    
879        /**
880         * Sets the purchasingAccountsPayableItemAssetDao attribute value.
881         * 
882         * @param purchasingAccountsPayableItemAssetDao The purchasingAccountsPayableItemAssetDao to set.
883         */
884        public void setPurchasingAccountsPayableItemAssetDao(PurchasingAccountsPayableItemAssetDao purchasingAccountsPayableItemAssetDao) {
885            this.purchasingAccountsPayableItemAssetDao = purchasingAccountsPayableItemAssetDao;
886        }
887    
888        /**
889         * Gets the purApLineService attribute.
890         * 
891         * @return Returns the purApLineService.
892         */
893        public PurApLineService getPurApLineService() {
894            return purApLineService;
895        }
896    
897        /**
898         * Sets the purApLineService attribute value.
899         * 
900         * @param purApLineService The purApLineService to set.
901         */
902        public void setPurApLineService(PurApLineService purApLineService) {
903            this.purApLineService = purApLineService;
904        }
905    
906        /**
907         * Gets the purApInfoService attribute.
908         * 
909         * @return Returns the purApInfoService.
910         */
911        public PurApInfoService getPurApInfoService() {
912            return purApInfoService;
913        }
914    
915        /**
916         * Sets the purApInfoService attribute value.
917         * 
918         * @param purApInfoService The purApInfoService to set.
919         */
920        public void setPurApInfoService(PurApInfoService purApInfoService) {
921            this.purApInfoService = purApInfoService;
922        }
923    
924    
925    }