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 }