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 }