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.document.service.impl; 017 018 import java.util.ArrayList; 019 import java.util.Collections; 020 import java.util.HashMap; 021 import java.util.HashSet; 022 import java.util.Iterator; 023 import java.util.List; 024 import java.util.Map; 025 import java.util.Set; 026 027 import org.apache.commons.lang.StringUtils; 028 import org.apache.log4j.Logger; 029 import org.kuali.kfs.coa.businessobject.ObjectCode; 030 import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService; 031 import org.kuali.kfs.module.cab.CabConstants; 032 import org.kuali.kfs.module.cab.CabPropertyConstants; 033 import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry; 034 import org.kuali.kfs.module.cab.businessobject.Pretag; 035 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableActionHistory; 036 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument; 037 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset; 038 import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount; 039 import org.kuali.kfs.module.cab.dataaccess.PurApLineDao; 040 import org.kuali.kfs.module.cab.document.service.PurApInfoService; 041 import org.kuali.kfs.module.cab.document.service.PurApLineService; 042 import org.kuali.kfs.module.cab.document.web.PurApLineSession; 043 import org.kuali.kfs.module.cam.CamsConstants; 044 import org.kuali.kfs.module.cam.document.service.AssetService; 045 import org.kuali.kfs.sys.context.SpringContext; 046 import org.kuali.rice.kns.service.BusinessObjectService; 047 import org.kuali.rice.kns.util.KualiDecimal; 048 import org.kuali.rice.kns.util.ObjectUtils; 049 import org.kuali.rice.kns.util.TypedArrayList; 050 import org.springframework.transaction.annotation.Transactional; 051 052 053 /** 054 * This class provides default implementations of {@link PurApLineService} 055 */ 056 @Transactional 057 public class PurApLineServiceImpl implements PurApLineService { 058 private static final Logger LOG = Logger.getLogger(PurApLineServiceImpl.class); 059 private BusinessObjectService businessObjectService; 060 private PurApLineDao purApLineDao; 061 private PurApInfoService purApInfoService; 062 private CapitalAssetManagementModuleService capitalAssetManagementModuleService; 063 064 /** 065 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#mergeLinesHasDifferentObjectSubTypes(java.util.List) 066 */ 067 public boolean mergeLinesHasDifferentObjectSubTypes(List<PurchasingAccountsPayableItemAsset> mergeLines) { 068 boolean invalid = false; 069 List<String> objectSubTypeList = new ArrayList<String>(); 070 071 // collect all objectSubTypes from item lines 072 for (PurchasingAccountsPayableItemAsset itemAsset : mergeLines) { 073 for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) { 074 account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT); 075 ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject(); 076 if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) { 077 objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode()); 078 } 079 } 080 } 081 082 // check if different objectSubTypes exist 083 if (!getAssetService().isObjectSubTypeCompatible(objectSubTypeList)) { 084 invalid = true; 085 } 086 return invalid; 087 } 088 089 /** 090 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#allocateLinesHasDifferentObjectSubTypes(java.util.List, 091 * org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset) 092 */ 093 public boolean allocateLinesHasDifferentObjectSubTypes(List<PurchasingAccountsPayableItemAsset> targetLines, PurchasingAccountsPayableItemAsset sourceLine) { 094 boolean invalid = false; 095 List<String> objectSubTypeList = new ArrayList<String>(); 096 097 // collect objectSubTypes from target item lines 098 for (PurchasingAccountsPayableItemAsset itemAsset : targetLines) { 099 for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) { 100 account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT); 101 ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject(); 102 if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) { 103 objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode()); 104 } 105 } 106 } 107 108 // collect objectSubTypes from source item line 109 if (ObjectUtils.isNotNull(sourceLine)) { 110 for (PurchasingAccountsPayableLineAssetAccount account : sourceLine.getPurchasingAccountsPayableLineAssetAccounts()) { 111 account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT); 112 ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject(); 113 if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) { 114 objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode()); 115 } 116 } 117 } 118 119 // check if different objectSubTypes exist 120 if (!getAssetService().isObjectSubTypeCompatible(objectSubTypeList)) { 121 invalid = true; 122 } 123 return invalid; 124 } 125 126 /** 127 * @see org.kuali.kfs.module.cab.document.service.PurApLineDocumentService#inActivateDocument(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument) 128 */ 129 public void conditionallyUpdateDocumentStatusAsProcessed(PurchasingAccountsPayableDocument selectedDoc) { 130 for (PurchasingAccountsPayableItemAsset item : selectedDoc.getPurchasingAccountsPayableItemAssets()) { 131 if (item.isActive()) { 132 return; 133 } 134 } 135 // set as processed when allocate/merge and all its items are in CAMs 136 selectedDoc.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS); 137 } 138 139 /** 140 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#resetSelectedValue(java.util.List) 141 */ 142 public void resetSelectedValue(List<PurchasingAccountsPayableDocument> purApDocs) { 143 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 144 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 145 item.setSelectedValue(false); 146 } 147 } 148 } 149 150 /** 151 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processAllocate(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset, 152 * java.util.List, org.kuali.kfs.module.cab.document.web.PurApLineSession, java.util.List) 153 */ 154 public boolean processAllocate(PurchasingAccountsPayableItemAsset allocateSourceLine, 155 List<PurchasingAccountsPayableItemAsset> allocateTargetLines, 156 List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, 157 List<PurchasingAccountsPayableDocument> purApDocs, 158 boolean initiateFromBatch) { 159 160 boolean allocatedIndicator = true; 161 // indicator of additional charge allocation 162 boolean allocateAddlChrgIndicator = allocateSourceLine.isAdditionalChargeNonTradeInIndicator() | allocateSourceLine.isTradeInAllowance(); 163 // Maintain this account List for update. So accounts already allocated won't take effect for account not allocated yet. 164 List<PurchasingAccountsPayableLineAssetAccount> newAccountList = new ArrayList<PurchasingAccountsPayableLineAssetAccount>(); 165 166 // For each account in the source item, allocate it to the target items. 167 for (PurchasingAccountsPayableLineAssetAccount sourceAccount : allocateSourceLine.getPurchasingAccountsPayableLineAssetAccounts()) { 168 sourceAccount.refresh(); 169 // Get allocate to target account list 170 List<PurchasingAccountsPayableLineAssetAccount> targetAccounts = getAllocateTargetAccounts(sourceAccount, allocateTargetLines, allocateAddlChrgIndicator); 171 if (!targetAccounts.isEmpty()) { 172 // Percentage amount to each target account 173 allocateByItemAccountAmount(sourceAccount, targetAccounts, newAccountList, actionsTakeHistory); 174 } 175 else { 176 allocatedIndicator = false; 177 break; 178 } 179 } 180 181 if (allocatedIndicator) { 182 postAllocateProcess(allocateSourceLine, allocateTargetLines, purApDocs, newAccountList, initiateFromBatch); 183 } 184 185 return allocatedIndicator; 186 } 187 188 189 /** 190 * Process after allocate. 191 * 192 * @param selectedLineItem 193 * @param allocateTargetLines 194 * @param purApDocs 195 * @param newAccountList 196 */ 197 protected void postAllocateProcess(PurchasingAccountsPayableItemAsset selectedLineItem, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, List<PurchasingAccountsPayableDocument> purApDocs, List<PurchasingAccountsPayableLineAssetAccount> newAccountList, boolean initiateFromBatch) { 198 // add new account into each item list 199 addNewAccountToItemList(newAccountList); 200 201 // update total cost and unit cost. 202 updateLineItemsCost(allocateTargetLines); 203 204 if (ObjectUtils.isNotNull(selectedLineItem.getPurchasingAccountsPayableDocument())) { 205 // remove allocate source line item. 206 selectedLineItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets().remove(selectedLineItem); 207 // when an line is removed from the document, we should check if it is the only active line in the document and 208 // in-activate document if yes. 209 conditionallyUpdateDocumentStatusAsProcessed(selectedLineItem.getPurchasingAccountsPayableDocument()); 210 } 211 212 // Adjust create asset and apply payment indicator only when allocate additional charges. 213 if (!initiateFromBatch && (selectedLineItem.isAdditionalChargeNonTradeInIndicator() || selectedLineItem.isTradeInAllowance())) { 214 setAssetIndicator(purApDocs); 215 } 216 217 // update status code as user modified for allocate target lines 218 if (!initiateFromBatch) { 219 for (PurchasingAccountsPayableItemAsset allocateTargetItem : allocateTargetLines) { 220 updateItemStatusAsUserModified(allocateTargetItem); 221 } 222 } 223 } 224 225 /** 226 * Build removable asset lock map from the processedItems list. We need to remove all asset locks hold be items which has been 227 * merged or allocated to other lines. 228 * 229 * @param processedItems 230 * @return 231 */ 232 protected Map<String, Set> getRemovableAssetLocks(List<PurchasingAccountsPayableItemAsset> processedItems) { 233 Map<String, Set> removableAssetLocks = new HashMap<String, Set>(); 234 235 for (PurchasingAccountsPayableItemAsset processedItem : processedItems) { 236 // For the time being, only for individual system, each item has its own asset numbers. 237 if (processedItem.getLockingInformation() != null && !CamsConstants.defaultLockingInformation.equals(processedItem.getLockingInformation())) { 238 addAssetLock(removableAssetLocks, processedItem); 239 } 240 else if (ObjectUtils.isNotNull(processedItem.getPurchasingAccountsPayableDocument())) { 241 // check other items if they are fully processed and can release the lock 242 List<PurchasingAccountsPayableItemAsset> remainingItems = processedItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets(); 243 boolean fullyProcessed = true; 244 for (PurchasingAccountsPayableItemAsset itemAsset : remainingItems) { 245 if (!CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equalsIgnoreCase(itemAsset.getActivityStatusCode())) { 246 fullyProcessed = false; 247 break; 248 } 249 } 250 if (fullyProcessed) { 251 // All the items are either merged or allocated to other document item. We should remove the asset lock 252 // retained by this document. 253 addAssetLock(removableAssetLocks, processedItem); 254 } 255 } 256 } 257 258 processedItems.clear(); 259 return removableAssetLocks; 260 } 261 262 protected void addAssetLock(Map<String, Set> removableAssetLocks, PurchasingAccountsPayableItemAsset processedItem) { 263 if (processedItem.getLockingInformation() == null) { 264 processedItem.setLockingInformation(CamsConstants.defaultLockingInformation); 265 } 266 if (removableAssetLocks.containsKey(processedItem.getDocumentNumber())) { 267 Set lockingInfoList = removableAssetLocks.get(processedItem.getDocumentNumber()); 268 lockingInfoList.add(processedItem.getLockingInformation()); 269 } 270 else { 271 Set lockingInfoList = new HashSet<String>(); 272 lockingInfoList.add(processedItem.getLockingInformation()); 273 removableAssetLocks.put(processedItem.getDocumentNumber(), lockingInfoList); 274 } 275 } 276 277 /** 278 * Reset item total cost and unit cost for each item. 279 * 280 * @param allocateTargetLines 281 */ 282 protected void updateLineItemsCost(List<PurchasingAccountsPayableItemAsset> lineItems) { 283 // update target item unit cost and total cost 284 for (PurchasingAccountsPayableItemAsset item : lineItems) { 285 setLineItemCost(item); 286 } 287 } 288 289 /** 290 * update account list for each line item 291 * 292 * @param updatedAccountList 293 */ 294 protected void addNewAccountToItemList(List<PurchasingAccountsPayableLineAssetAccount> newAccountList) { 295 PurchasingAccountsPayableItemAsset lineItem = null; 296 for (PurchasingAccountsPayableLineAssetAccount newAccount : newAccountList) { 297 lineItem = newAccount.getPurchasingAccountsPayableItemAsset(); 298 if (ObjectUtils.isNotNull(lineItem) && ObjectUtils.isNotNull(lineItem.getPurchasingAccountsPayableLineAssetAccounts())) { 299 lineItem.getPurchasingAccountsPayableLineAssetAccounts().add(newAccount); 300 } 301 } 302 } 303 304 /** 305 * Set line item total cost and unit cost. 306 * 307 * @param item 308 */ 309 protected void setLineItemCost(PurchasingAccountsPayableItemAsset item) { 310 KualiDecimal totalCost = calculateItemAssetTotalCost(item); 311 item.setTotalCost(totalCost); 312 setItemAssetUnitCost(item, totalCost); 313 } 314 315 /** 316 * Allocate one account line to target account lines percentage based on the account line amount. 317 * 318 * @param sourceAccount Account line to be allocated. 319 * @param targetAccounts Account lines which accept amount. 320 */ 321 protected void allocateByItemAccountAmount(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableLineAssetAccount> targetAccounts, List<PurchasingAccountsPayableLineAssetAccount> newAccountList, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory) { 322 KualiDecimal targetAccountsTotalAmount = KualiDecimal.ZERO; 323 KualiDecimal sourceAccountTotalAmount = sourceAccount.getItemAccountTotalAmount(); 324 KualiDecimal amountAllocated = KualiDecimal.ZERO; 325 KualiDecimal additionalAmount = null; 326 327 // Calculate the targetAccountTotalAmount. Ignore the sign of the account amount when proportionally allocate based on 328 // account amount 329 for (PurchasingAccountsPayableLineAssetAccount targetAccount : targetAccounts) { 330 targetAccountsTotalAmount = targetAccountsTotalAmount.add(targetAccount.getItemAccountTotalAmount().abs()); 331 } 332 333 for (Iterator<PurchasingAccountsPayableLineAssetAccount> iterator = targetAccounts.iterator(); iterator.hasNext();) { 334 PurchasingAccountsPayableLineAssetAccount targetAccount = (PurchasingAccountsPayableLineAssetAccount) iterator.next(); 335 if (iterator.hasNext()) { 336 // Not working on the last node. Calculate additional charge amount by percentage. Ignore the sign of the account 337 // amount when proportionally allocate based on account amount 338 additionalAmount = targetAccount.getItemAccountTotalAmount().abs().multiply(sourceAccountTotalAmount).divide(targetAccountsTotalAmount); 339 amountAllocated = amountAllocated.add(additionalAmount); 340 } 341 else { 342 // Working on the last node, set the additional charge amount to the rest of sourceAccountTotalAmount. 343 additionalAmount = sourceAccountTotalAmount.subtract(amountAllocated); 344 } 345 346 // Code below mainly handle grouping account lines if they're from the same GL. 347 PurchasingAccountsPayableLineAssetAccount newAccount = getMatchingFromAccountList(targetAccounts, sourceAccount.getGeneralLedgerAccountIdentifier(), targetAccount); 348 if (newAccount != null) { 349 // If exists the same account line and GL entry, update its itemAccountTotalAmount. This account line could be other 350 // than targetAccount, but must belong to the same line item. 351 updateAccountAmount(additionalAmount, newAccount); 352 } 353 else { 354 // If exist account just created, grouping them and update the account amount. 355 newAccount = getMatchingFromAccountList(newAccountList, sourceAccount.getGeneralLedgerAccountIdentifier(), targetAccount); 356 if (newAccount != null) { 357 updateAccountAmount(additionalAmount, newAccount); 358 } 359 else { 360 // If the target account is a different GL entry, create a new account and attach to this line item. 361 newAccount = new PurchasingAccountsPayableLineAssetAccount(targetAccount.getPurchasingAccountsPayableItemAsset(), sourceAccount.getGeneralLedgerAccountIdentifier()); 362 newAccount.setItemAccountTotalAmount(additionalAmount); 363 newAccount.setGeneralLedgerEntry(sourceAccount.getGeneralLedgerEntry()); 364 newAccountList.add(newAccount); 365 } 366 } 367 368 // add to action history 369 addAllocateHistory(sourceAccount, actionsTakeHistory, additionalAmount, newAccount); 370 } 371 } 372 373 /** 374 * Save allocate action into session object. 375 * 376 * @param sourceAccount 377 * @param actionsTakeHistory 378 * @param additionalAmount 379 * @param newAccount 380 */ 381 protected void addAllocateHistory(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, KualiDecimal additionalAmount, PurchasingAccountsPayableLineAssetAccount newAccount) { 382 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(sourceAccount.getPurchasingAccountsPayableItemAsset(), newAccount.getPurchasingAccountsPayableItemAsset(), CabConstants.Actions.ALLOCATE); 383 newAction.setGeneralLedgerAccountIdentifier(sourceAccount.getGeneralLedgerAccountIdentifier()); 384 newAction.setItemAccountTotalAmount(additionalAmount); 385 newAction.setAccountsPayableItemQuantity(sourceAccount.getPurchasingAccountsPayableItemAsset().getAccountsPayableItemQuantity()); 386 actionsTakeHistory.add(newAction); 387 } 388 389 390 /** 391 * Search matching account in targetAccounts by glIdentifier. 392 * 393 * @param targetAccounts 394 * @param glIdentifier 395 * @return 396 */ 397 protected PurchasingAccountsPayableLineAssetAccount getFromTargetAccountList(List<PurchasingAccountsPayableLineAssetAccount> targetAccounts, Long glIdentifier) { 398 for (PurchasingAccountsPayableLineAssetAccount account : targetAccounts) { 399 if (account.getGeneralLedgerAccountIdentifier().equals(glIdentifier)) { 400 return account; 401 } 402 } 403 return null; 404 } 405 406 /** 407 * Update targetAccount by additionalAmount. 408 * 409 * @param additionalAmount 410 * @param targetAccount 411 */ 412 protected void updateAccountAmount(KualiDecimal additionalAmount, PurchasingAccountsPayableLineAssetAccount targetAccount) { 413 KualiDecimal baseAmount = targetAccount.getItemAccountTotalAmount(); 414 targetAccount.setItemAccountTotalAmount(baseAmount != null ? baseAmount.add(additionalAmount) : additionalAmount); 415 } 416 417 418 /** 419 * Searching in accountList by glIdentifier for matching account which associated with the same item as targetAccount. 420 * 421 * @param accountList 422 * @param glIdentifier 423 * @param targetAccount 424 * @return 425 */ 426 protected PurchasingAccountsPayableLineAssetAccount getMatchingFromAccountList(List<PurchasingAccountsPayableLineAssetAccount> accountList, Long glIdentifier, PurchasingAccountsPayableLineAssetAccount targetAccount) { 427 for (PurchasingAccountsPayableLineAssetAccount account : accountList) { 428 if (StringUtils.equalsIgnoreCase(targetAccount.getDocumentNumber(), account.getDocumentNumber()) && targetAccount.getAccountsPayableLineItemIdentifier().equals(account.getAccountsPayableLineItemIdentifier()) && targetAccount.getCapitalAssetBuilderLineNumber().equals(account.getCapitalAssetBuilderLineNumber()) && glIdentifier.equals(account.getGeneralLedgerAccountIdentifier())) { 429 return account; 430 } 431 } 432 return null; 433 } 434 435 /** 436 * Get the target account lines which will be used for allocate. 437 * 438 * @param sourceAccount 439 * @param lineItems 440 * @param addtionalCharge 441 * @return 442 */ 443 protected List<PurchasingAccountsPayableLineAssetAccount> getAllocateTargetAccounts(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, boolean addtionalCharge) { 444 GeneralLedgerEntry candidateEntry = null; 445 GeneralLedgerEntry sourceEntry = sourceAccount.getGeneralLedgerEntry(); 446 List<PurchasingAccountsPayableLineAssetAccount> matchingAccounts = new ArrayList<PurchasingAccountsPayableLineAssetAccount>(); 447 List<PurchasingAccountsPayableLineAssetAccount> allAccounts = new ArrayList<PurchasingAccountsPayableLineAssetAccount>(); 448 449 // For additional charge allocation, target account selection is based on account lines with the same account number and 450 // object code. If no matching, select all account lines. For line item to line items, select all account lines as target. 451 for (PurchasingAccountsPayableItemAsset item : allocateTargetLines) { 452 for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) { 453 //KFSMI-5122: We need to refresh account general ledger entry so that the gl entries become visible as candidateEntry 454 account.refreshReferenceObject("generalLedgerEntry"); 455 candidateEntry = account.getGeneralLedgerEntry(); 456 457 if (ObjectUtils.isNotNull(candidateEntry)) { 458 // For additional charge, select matching account when account number and object code both match. 459 if (addtionalCharge && StringUtils.equalsIgnoreCase(sourceEntry.getChartOfAccountsCode(), candidateEntry.getChartOfAccountsCode()) && StringUtils.equalsIgnoreCase(sourceEntry.getAccountNumber(), candidateEntry.getAccountNumber()) && StringUtils.equalsIgnoreCase(sourceEntry.getFinancialObjectCode(), candidateEntry.getFinancialObjectCode())) { 460 matchingAccounts.add(account); 461 } 462 } 463 464 allAccounts.add(account); 465 } 466 } 467 468 return matchingAccounts.isEmpty() ? allAccounts : matchingAccounts; 469 } 470 471 /** 472 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#getAllocateTargetLines(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset, 473 * java.util.List) 474 */ 475 public List<PurchasingAccountsPayableItemAsset> getAllocateTargetLines(PurchasingAccountsPayableItemAsset selectedLineItem, List<PurchasingAccountsPayableDocument> purApDocs) { 476 List<PurchasingAccountsPayableItemAsset> targetLineItems = new TypedArrayList(PurchasingAccountsPayableItemAsset.class); 477 478 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 479 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 480 // If selected Line is additional charge line, get target lines from the same document. 481 // Else if selected Line is trade in allowance, target lines are item lines with TI indicator set. 482 // Otherwise, select items with check box set. 483 if (item.isActive() && item != selectedLineItem && ((selectedLineItem.isAdditionalChargeNonTradeInIndicator() && !item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && StringUtils.equalsIgnoreCase(selectedLineItem.getDocumentNumber(), item.getDocumentNumber())) || (selectedLineItem.isTradeInAllowance() && item.isItemAssignedToTradeInIndicator()) || (item.isSelectedValue()))) { 484 targetLineItems.add(item); 485 } 486 } 487 } 488 return targetLineItems; 489 } 490 491 /** 492 * Get selected merge lines. If this is merge all action, we need to manually append all additional charge lines since no select 493 * box associated with them. 494 * 495 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#getSelectedMergeLines(boolean, java.util.List) 496 */ 497 public List<PurchasingAccountsPayableItemAsset> getSelectedMergeLines(boolean isMergeAll, List<PurchasingAccountsPayableDocument> purApDocs) { 498 List<PurchasingAccountsPayableItemAsset> mergeLines = new TypedArrayList(PurchasingAccountsPayableItemAsset.class); 499 boolean excludeTradeInAllowance = false; 500 501 // Handle one exception for merge all: when we have TI allowance but no TI indicator line, we should exclude 502 if (isMergeAll && !isTradeInIndicatorExistInAllLines(purApDocs) && isTradeInAllowanceExist(purApDocs)) { 503 excludeTradeInAllowance = true; 504 } 505 506 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 507 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 508 // If not merge all action, select items are the merge lines. If it is merge all action, all lines should be 509 // candidate merge lines except trade-in allowance line when there is no trade-in indicator line. 510 if ((!isMergeAll && item.isSelectedValue()) || (isMergeAll && (!excludeTradeInAllowance || !item.isTradeInAllowance()))) { 511 mergeLines.add(item); 512 // setup non-persistent relationship from item to document. 513 item.setPurchasingAccountsPayableDocument(purApDoc); 514 } 515 } 516 } 517 return mergeLines; 518 } 519 520 /** 521 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isTradeInAllowanceExist(java.util.List) 522 */ 523 public boolean isTradeInAllowanceExist(List<PurchasingAccountsPayableDocument> purApDocs) { 524 boolean tradeInAllowance = false; 525 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 526 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 527 if (item.isTradeInAllowance()) { 528 tradeInAllowance = true; 529 break; 530 } 531 } 532 } 533 return tradeInAllowance; 534 } 535 536 /** 537 * Check if TI indicator exists in all form lines 538 * 539 * @param purApDocs 540 * @return 541 */ 542 protected boolean isTradeInIndicatorExistInAllLines(List<PurchasingAccountsPayableDocument> purApDocs) { 543 boolean tradeInIndicator = false; 544 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 545 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 546 if (item.isItemAssignedToTradeInIndicator()) { 547 tradeInIndicator = true; 548 break; 549 } 550 } 551 } 552 return tradeInIndicator; 553 } 554 555 /** 556 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isTradeInIndicatorExist(java.util.List) 557 */ 558 public boolean isTradeInIndExistInSelectedLines(List<PurchasingAccountsPayableItemAsset> itemAssets) { 559 boolean tradeInIndicator = false; 560 for (PurchasingAccountsPayableItemAsset item : itemAssets) { 561 if (item.isItemAssignedToTradeInIndicator()) { 562 tradeInIndicator = true; 563 break; 564 } 565 } 566 return tradeInIndicator; 567 } 568 569 /** 570 * If item assets are from the same document, we can ignore additional charges pending. 571 * 572 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isAdditionalChargePending(java.util.List) 573 */ 574 public boolean isAdditionalChargePending(List<PurchasingAccountsPayableItemAsset> itemAssets) { 575 boolean additionalChargePending = false; 576 Boolean diffDocment = false; 577 PurchasingAccountsPayableItemAsset firstAsset = itemAssets.get(0); 578 PurchasingAccountsPayableItemAsset lastAsset = itemAssets.get(itemAssets.size() - 1); 579 580 // Check if itemAssets are in different PurAp Document. itemAssets is a sorted list which has item assets from the same 581 // document grouping together. 582 if (ObjectUtils.isNotNull(firstAsset) && ObjectUtils.isNotNull(lastAsset) && !firstAsset.getDocumentNumber().equalsIgnoreCase(lastAsset.getDocumentNumber())) { 583 diffDocment = true; 584 } 585 586 // check if item assets from different document have additional charges not allocated yet. Bring all lines in 587 // the same document as checking candidate. 588 if (diffDocment) { 589 for (PurchasingAccountsPayableItemAsset item : itemAssets) { 590 if (ObjectUtils.isNotNull(item.getPurchasingAccountsPayableDocument())) { 591 for (PurchasingAccountsPayableItemAsset itemLine : item.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets()) { 592 if (itemLine.isAdditionalChargeNonTradeInIndicator()) { 593 additionalChargePending = true; 594 return additionalChargePending; 595 } 596 } 597 } 598 } 599 } 600 return additionalChargePending; 601 } 602 603 /** 604 * Check if the merge action is merge all. 605 * 606 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isMergeAllAction(java.util.List) 607 */ 608 public boolean isMergeAllAction(List<PurchasingAccountsPayableDocument> purApDocs) { 609 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 610 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 611 // When there is one item line not selected, mergeAll is false 612 if (!item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && !item.isSelectedValue()) { 613 return false; 614 } 615 } 616 } 617 return true; 618 } 619 620 /** 621 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isAdditionalChargeExistInAllLines(java.util.List) 622 */ 623 public boolean isAdditionalChargeExistInAllLines(List<PurchasingAccountsPayableDocument> purApDocs) { 624 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 625 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 626 if (item.isAdditionalChargeNonTradeInIndicator()) { 627 return true; 628 } 629 } 630 } 631 return false; 632 } 633 634 /** 635 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processMerge(java.util.List) 636 */ 637 public void processMerge(List<PurchasingAccountsPayableItemAsset> mergeLines, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, boolean isMergeAll) { 638 PurchasingAccountsPayableItemAsset sourceItem = null; 639 PurchasingAccountsPayableLineAssetAccount targetAccount = null; 640 // use the first item and its accounts as the target 641 PurchasingAccountsPayableItemAsset firstItem = mergeLines.get(0); 642 List<PurchasingAccountsPayableLineAssetAccount> firstAccountList = firstItem.getPurchasingAccountsPayableLineAssetAccounts(); 643 644 // Merge accounts starting from the second item to the last one. 645 for (int i = 1; i < mergeLines.size(); i++) { 646 sourceItem = mergeLines.get(i); 647 for (PurchasingAccountsPayableLineAssetAccount account : sourceItem.getPurchasingAccountsPayableLineAssetAccounts()) { 648 // Check if we can grouping the accounts. If yes, update the account amount without moving account line. 649 targetAccount = getFromTargetAccountList(firstAccountList, account.getGeneralLedgerAccountIdentifier()); 650 if (targetAccount != null) { 651 updateAccountAmount(account.getItemAccountTotalAmount(), targetAccount); 652 } 653 else { 654 // move account from source to target 655 account.setDocumentNumber(firstItem.getDocumentNumber()); 656 account.setAccountsPayableLineItemIdentifier(firstItem.getAccountsPayableLineItemIdentifier()); 657 account.setCapitalAssetBuilderLineNumber(firstItem.getCapitalAssetBuilderLineNumber()); 658 account.setPurchasingAccountsPayableItemAsset(firstItem); 659 firstAccountList.add(account); 660 } 661 } 662 } 663 664 // update action history, remove lines before merge and clean up the user input 665 postMergeProcess(mergeLines, actionsTakeHistory, isMergeAll); 666 } 667 668 /** 669 * Process after merge. 670 * 671 * @param mergeLines 672 * @param purApLineSession 673 * @param isMergeAll 674 */ 675 protected void postMergeProcess(List<PurchasingAccountsPayableItemAsset> mergeLines, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, boolean isMergeAll) { 676 String actionTypeCode = isMergeAll ? CabConstants.Actions.MERGE_ALL : CabConstants.Actions.MERGE; 677 PurchasingAccountsPayableItemAsset targetItem = mergeLines.get(0); 678 679 // set unit cost and total cost 680 setLineItemCost(targetItem); 681 targetItem.setItemAssignedToTradeInIndicator(false); 682 683 // For all merge source lines(the first line is considered as target technically), remove it from the document and update in 684 // the action history. 685 for (int i = 1; i < mergeLines.size(); i++) { 686 PurchasingAccountsPayableItemAsset sourceItem = mergeLines.get(i); 687 688 // Update the action history. 689 addMergeHistory(actionsTakeHistory, actionTypeCode, sourceItem, targetItem); 690 691 if (ObjectUtils.isNotNull(sourceItem.getPurchasingAccountsPayableDocument())) { 692 // remove mergeLines from the document 693 sourceItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets().remove(sourceItem); 694 // if all active lines are merged to other line, we need to in-activate the current document 695 conditionallyUpdateDocumentStatusAsProcessed(sourceItem.getPurchasingAccountsPayableDocument()); 696 } 697 } 698 // set the target item itemLineNumber for pre-tagging 699 Integer poId = targetItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier(); 700 if (poId != null) { 701 Pretag targetPretag = getTargetPretag(mergeLines, poId); 702 if (targetPretag != null) { 703 targetItem.setItemLineNumber(targetPretag.getItemLineNumber()); 704 } 705 } 706 707 // update create asset/ apply payment indicator if any of the merged lines has the indicator set. 708 updateAssetIndicatorAfterMerge(mergeLines); 709 710 // update activity status code as modified 711 updateItemStatusAsUserModified(targetItem); 712 } 713 714 /** 715 * Update create asset and apply payment indicators after merge. 716 * 717 * @param mergeLines 718 */ 719 protected void updateAssetIndicatorAfterMerge(List<PurchasingAccountsPayableItemAsset> mergeLines) { 720 boolean existCreateAsset = false; 721 boolean existApplyPayment = false; 722 PurchasingAccountsPayableItemAsset targetItem = mergeLines.get(0); 723 // set indicator if any of the source item set it or target item set it. 724 for (int i = 1; i < mergeLines.size(); i++) { 725 PurchasingAccountsPayableItemAsset sourceItem = mergeLines.get(i); 726 existCreateAsset |= sourceItem.isCreateAssetIndicator(); 727 existApplyPayment |= sourceItem.isApplyPaymentIndicator(); 728 } 729 targetItem.setCreateAssetIndicator(targetItem.isCreateAssetIndicator() | existCreateAsset); 730 targetItem.setApplyPaymentIndicator(targetItem.isApplyPaymentIndicator() | existApplyPayment); 731 } 732 733 /** 734 * Get the first pre-tag for given itemLines 735 * 736 * @param itemLines 737 * @param purchaseOrderIdentifier 738 * @return 739 */ 740 protected Pretag getTargetPretag(List<PurchasingAccountsPayableItemAsset> itemLines, Integer purchaseOrderIdentifier) { 741 for (PurchasingAccountsPayableItemAsset item : itemLines) { 742 Pretag newTag = getPreTagLineItem(purchaseOrderIdentifier, item.getItemLineNumber()); 743 744 if (isPretaggingExisting(newTag)) 745 return newTag; 746 } 747 748 return null; 749 } 750 751 /** 752 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isPretaggingExisting(org.kuali.kfs.module.cab.businessobject.Pretag) 753 */ 754 public boolean isPretaggingExisting(Pretag newTag) { 755 return ObjectUtils.isNotNull(newTag) && newTag.getPretagDetails() != null && !newTag.getPretagDetails().isEmpty(); 756 } 757 758 759 /** 760 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#isMultipleTagExisting(java.lang.Integer, java.util.Set) 761 */ 762 public boolean isMultipleTagExisting(Integer purchaseOrderIdentifier, Set<Integer> itemLineNumbers) { 763 Pretag firstTag = null; 764 for (Iterator iterator = itemLineNumbers.iterator(); iterator.hasNext();) { 765 Integer itemLineNumber = (Integer) iterator.next(); 766 Pretag newTag = getPreTagLineItem(purchaseOrderIdentifier, itemLineNumber); 767 768 if (isPretaggingExisting(newTag)) 769 if (firstTag != null) { 770 // find the second preTagging item 771 return true; 772 } 773 else { 774 firstTag = newTag; 775 } 776 } 777 778 return false; 779 } 780 781 /** 782 * Add merge action to the action history. 783 * 784 * @param purApLineSession 785 * @param isMergeAllAction 786 * @param firstItem 787 * @param item 788 */ 789 protected void addMergeHistory(List<PurchasingAccountsPayableActionHistory> actionsTakenHistory, String actionTypeCode, PurchasingAccountsPayableItemAsset sourceItem, PurchasingAccountsPayableItemAsset targetItem) { 790 // create action history records for each account from the source lines. 791 for (PurchasingAccountsPayableLineAssetAccount sourceAccount : sourceItem.getPurchasingAccountsPayableLineAssetAccounts()) { 792 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(sourceItem, targetItem, actionTypeCode); 793 newAction.setAccountsPayableItemQuantity(sourceItem.getAccountsPayableItemQuantity()); 794 newAction.setItemAccountTotalAmount(sourceAccount.getItemAccountTotalAmount()); 795 newAction.setGeneralLedgerAccountIdentifier(sourceAccount.getGeneralLedgerAccountIdentifier()); 796 actionsTakenHistory.add(newAction); 797 } 798 } 799 800 801 /** 802 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processPercentPayment(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset) 803 */ 804 public void processPercentPayment(PurchasingAccountsPayableItemAsset itemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakenHistory) { 805 KualiDecimal oldQty = itemAsset.getAccountsPayableItemQuantity(); 806 KualiDecimal newQty = new KualiDecimal(1); 807 // update quantity, total cost and unit cost. 808 if (oldQty.isLessThan(newQty)) { 809 itemAsset.setAccountsPayableItemQuantity(newQty); 810 setLineItemCost(itemAsset); 811 // add to action history 812 addPercentPaymentHistory(actionsTakenHistory, itemAsset, oldQty); 813 // update status code 814 updateItemStatusAsUserModified(itemAsset); 815 } 816 } 817 818 819 /** 820 * Update activity status code when percent payment/split/allocate/merge action taken. 821 * 822 * @param itemAsset 823 */ 824 protected void updateItemStatusAsUserModified(PurchasingAccountsPayableItemAsset itemAsset) { 825 itemAsset.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED); 826 for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) { 827 account.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED); 828 } 829 830 itemAsset.getPurchasingAccountsPayableDocument().setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED); 831 832 } 833 834 /** 835 * Update action history for the percent payment action. 836 * 837 * @param actionsTaken 838 * @param item 839 * @param oldQty 840 */ 841 protected void addPercentPaymentHistory(List<PurchasingAccountsPayableActionHistory> actionsTakenHistory, PurchasingAccountsPayableItemAsset item, KualiDecimal oldQty) { 842 // create and set up one action history record for this action 843 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(item, item, CabConstants.Actions.PERCENT_PAYMENT); 844 // record quantity before percent payment into action history 845 newAction.setAccountsPayableItemQuantity(oldQty); 846 actionsTakenHistory.add(newAction); 847 } 848 849 /** 850 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processSplit(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset) 851 */ 852 public void processSplit(PurchasingAccountsPayableItemAsset splitItemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory) { 853 PurchasingAccountsPayableDocument purApDoc = splitItemAsset.getPurchasingAccountsPayableDocument(); 854 // update activity status code for split item. it will be propogated to new created item and its accounts. 855 updateItemStatusAsUserModified(splitItemAsset); 856 857 // create a new item asset from the current item asset. 858 PurchasingAccountsPayableItemAsset newItemAsset = new PurchasingAccountsPayableItemAsset(splitItemAsset); 859 860 // set cab line number 861 newItemAsset.setCapitalAssetBuilderLineNumber(getMaxCabLineNumber(splitItemAsset, purApDoc) + 1); 862 863 newItemAsset.setAccountsPayableItemQuantity(splitItemAsset.getSplitQty()); 864 865 // Set account list for new item asset and update current account amount value. 866 createAccountsForNewItemAsset(splitItemAsset, newItemAsset); 867 // set unit cost and total cost in new item 868 setLineItemCost(newItemAsset); 869 870 // Adjust current item asset quantity, total cost and unit cost 871 splitItemAsset.setAccountsPayableItemQuantity(splitItemAsset.getAccountsPayableItemQuantity().subtract(splitItemAsset.getSplitQty())); 872 setLineItemCost(splitItemAsset); 873 874 // add the new item into document and sort. 875 purApDoc.getPurchasingAccountsPayableItemAssets().add(newItemAsset); 876 Collections.sort(purApDoc.getPurchasingAccountsPayableItemAssets()); 877 878 // Add to action history 879 addSplitHistory(splitItemAsset, newItemAsset, actionsTakeHistory); 880 881 // clear up user input 882 splitItemAsset.setSplitQty(null); 883 } 884 885 886 /** 887 * Get the max cab line #. As part of the primary key, it should be the max value among the form item list and DB. 888 * 889 * @param splitItemAsset 890 * @param purApDoc 891 * @return 892 */ 893 protected int getMaxCabLineNumber(PurchasingAccountsPayableItemAsset splitItemAsset, PurchasingAccountsPayableDocument purApDoc) { 894 // get the max CAB line number in DB. 895 Integer maxDBCabLineNbr = purApLineDao.getMaxCabLineNumber(splitItemAsset.getDocumentNumber(), splitItemAsset.getAccountsPayableLineItemIdentifier()); 896 // get the max CAB line number in form. 897 int availableCabLineNbr = getMaxCabLineNbrForItemInForm(purApDoc, splitItemAsset); 898 899 if (maxDBCabLineNbr.intValue() > availableCabLineNbr) { 900 availableCabLineNbr = maxDBCabLineNbr.intValue(); 901 } 902 return availableCabLineNbr; 903 } 904 905 /** 906 * Search the current active items and return the max CAB line # for split new item . 907 * 908 * @param purApDoc 909 * @param currentItemAsset 910 * @return 911 */ 912 protected int getMaxCabLineNbrForItemInForm(PurchasingAccountsPayableDocument purApDoc, PurchasingAccountsPayableItemAsset currentItemAsset) { 913 int maxCabLineNbr = 0; 914 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 915 if (item.getDocumentNumber().equalsIgnoreCase(currentItemAsset.getDocumentNumber()) && item.getAccountsPayableLineItemIdentifier().equals(currentItemAsset.getAccountsPayableLineItemIdentifier()) && item.getCapitalAssetBuilderLineNumber().intValue() > maxCabLineNbr) { 916 maxCabLineNbr = item.getCapitalAssetBuilderLineNumber().intValue(); 917 } 918 919 } 920 return maxCabLineNbr; 921 } 922 923 /** 924 * Update action history for a split action. 925 * 926 * @param currentItemAsset 927 * @param newItemAsset 928 * @param actionsTaken 929 */ 930 protected void addSplitHistory(PurchasingAccountsPayableItemAsset currentItemAsset, PurchasingAccountsPayableItemAsset newItemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakenHistory) { 931 // for each account moved from original item to new item, create one action history record 932 for (PurchasingAccountsPayableLineAssetAccount account : newItemAsset.getPurchasingAccountsPayableLineAssetAccounts()) { 933 PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(currentItemAsset, newItemAsset, CabConstants.Actions.SPLIT); 934 newAction.setGeneralLedgerAccountIdentifier(account.getGeneralLedgerAccountIdentifier()); 935 // quantity moved from original item to new item 936 newAction.setAccountsPayableItemQuantity(newItemAsset.getAccountsPayableItemQuantity()); 937 // account amount moved to new item 938 newAction.setItemAccountTotalAmount(account.getItemAccountTotalAmount()); 939 actionsTakenHistory.add(newAction); 940 } 941 } 942 943 944 /** 945 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#processSaveBusinessObjects(java.util.List, 946 * org.kuali.kfs.module.cab.document.web.PurApLineSession) 947 */ 948 public void processSaveBusinessObjects(List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession) { 949 // Get removable asset locks which could be generated by allocate or merge when items removed and the lock should be 950 // released. 951 Map<String, Set> removableAssetLocks = getRemovableAssetLocks(purApLineSession.getProcessedItems()); 952 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 953 // auto save items(including deleted items) and accounts due to auto-update setting in OJB. 954 businessObjectService.save(purApDoc); 955 } 956 // remove asset locks 957 for (String lockingDocumentNbr : removableAssetLocks.keySet()) { 958 Set<String> lockingInfoList = removableAssetLocks.get(lockingDocumentNbr); 959 for (String lockingInfo : lockingInfoList) { 960 if (this.getCapitalAssetManagementModuleService().isAssetLockedByCurrentDocument(lockingDocumentNbr, lockingInfo)) { 961 this.getCapitalAssetManagementModuleService().deleteAssetLocks(lockingDocumentNbr, lockingInfo); 962 } 963 } 964 } 965 966 if (purApLineSession != null) { 967 // save to action history table 968 List<PurchasingAccountsPayableActionHistory> historyList = purApLineSession.getActionsTakenHistory(); 969 if (historyList != null && !historyList.isEmpty()) { 970 businessObjectService.save(historyList); 971 historyList.clear(); 972 } 973 974 // save to generalLedgerEntry table 975 List<GeneralLedgerEntry> glUpdateList = purApLineSession.getGlEntryUpdateList(); 976 if (glUpdateList != null && !glUpdateList.isEmpty()) { 977 businessObjectService.save(glUpdateList); 978 glUpdateList.clear(); 979 } 980 981 } 982 } 983 984 985 /** 986 * Create asset account list for new item asset and update the current account amount. 987 * 988 * @param oldItemAsset old line item. 989 * @param newItemAsset new line item. 990 */ 991 protected void createAccountsForNewItemAsset(PurchasingAccountsPayableItemAsset currentItemAsset, PurchasingAccountsPayableItemAsset newItemAsset) { 992 KualiDecimal currentQty = currentItemAsset.getAccountsPayableItemQuantity(); 993 KualiDecimal splitQty = currentItemAsset.getSplitQty(); 994 List<PurchasingAccountsPayableLineAssetAccount> newAccountsList = newItemAsset.getPurchasingAccountsPayableLineAssetAccounts(); 995 PurchasingAccountsPayableLineAssetAccount newAccount; 996 for (PurchasingAccountsPayableLineAssetAccount currentAccount : currentItemAsset.getPurchasingAccountsPayableLineAssetAccounts()) { 997 // create accounts for new item asset. 998 newAccount = new PurchasingAccountsPayableLineAssetAccount(newItemAsset, currentAccount.getGeneralLedgerAccountIdentifier()); 999 newAccount.setItemAccountTotalAmount(currentAccount.getItemAccountTotalAmount().multiply(splitQty).divide(currentQty)); 1000 newAccount.setGeneralLedgerEntry(currentAccount.getGeneralLedgerEntry()); 1001 newAccountsList.add(newAccount); 1002 1003 // Adjust current account amount for split item( subtract new account amount for original amount) 1004 currentAccount.setItemAccountTotalAmount(currentAccount.getItemAccountTotalAmount().subtract(newAccount.getItemAccountTotalAmount())); 1005 } 1006 } 1007 1008 1009 /** 1010 * Set object code by the first one from the accounting lines. 1011 * 1012 * @param item Selected line item. 1013 */ 1014 protected void setFirstFinancialObjectCode(PurchasingAccountsPayableItemAsset item) { 1015 String firstFinancialObjectCode = null; 1016 for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) { 1017 if (ObjectUtils.isNotNull(account.getGeneralLedgerEntry())) { 1018 firstFinancialObjectCode = account.getGeneralLedgerEntry().getFinancialObjectCode(); 1019 break; 1020 } 1021 } 1022 item.setFirstFincialObjectCode(firstFinancialObjectCode); 1023 } 1024 1025 1026 /** 1027 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#buildPurApItemAssetList(java.util.List) 1028 */ 1029 public void buildPurApItemAssetList(List<PurchasingAccountsPayableDocument> purApDocs) { 1030 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 1031 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 1032 // set item non-persistent fields from PurAp PREQ/CM item tables 1033 purApInfoService.setAccountsPayableItemsFromPurAp(item, purApDoc.getDocumentTypeCode()); 1034 1035 // set line item unit cost and total cost 1036 setLineItemCost(item); 1037 1038 // set financial object code 1039 setFirstFinancialObjectCode(item); 1040 } 1041 // For display purpose, move additional charges including trade-in below item lines. 1042 Collections.sort(purApDoc.getPurchasingAccountsPayableItemAssets()); 1043 } 1044 1045 // set CAMS Transaction type from PurAp 1046 purApInfoService.setCamsTransactionFromPurAp(purApDocs); 1047 1048 // set create asset/apply payment indicator which are used to control display two buttons. 1049 setAssetIndicator(purApDocs); 1050 1051 } 1052 1053 1054 /** 1055 * @see org.kuali.kfs.module.cab.document.service.PurApLineService#getPreTagLineItem(java.lang.String, java.lang.Integer) 1056 */ 1057 public Pretag getPreTagLineItem(Integer purchaseOrderIdentifier, Integer lineItemNumber) { 1058 1059 if (purchaseOrderIdentifier == null || lineItemNumber == null) { 1060 return null; 1061 } 1062 1063 Map<String, Object> pKeys = new HashMap<String, Object>(); 1064 1065 pKeys.put(CabPropertyConstants.Pretag.PURCHASE_ORDER_NUMBER, purchaseOrderIdentifier); 1066 pKeys.put(CabPropertyConstants.Pretag.ITEM_LINE_NUMBER, lineItemNumber); 1067 return (Pretag) businessObjectService.findByPrimaryKey(Pretag.class, pKeys); 1068 } 1069 1070 /** 1071 * Set create asset and apply payment indicator. These two indicators are referenced by jsp to control display of these two 1072 * buttons. How to set these two indicators is based on the business rules. We need to put the following situations into 1073 * consideration. Since we move allocate additional charge allocation to CAB batch, we bring over addl charge lines only when 1074 * they are the only items in the document or they are from the FO changes. To accommodate this, we relax the rules and defined 1075 * as: 1076 * <p> 1077 * 1. If the line is trade-in allowance and without trade-in indicator items, we open these two buttons. 1078 * <p> 1079 * 2. For line items, if it has trade-in indicator set, there must be no trade-in allowance pending for allocation. Trade-in 1080 * allowance could from other document but share the same po_id. 1081 * 1082 * @param purApDocs 1083 */ 1084 protected void setAssetIndicator(List<PurchasingAccountsPayableDocument> purApDocs) { 1085 // get the trade-in allowance in the form-wise 1086 boolean existTradeInAllowance = isTradeInAllowanceExist(purApDocs); 1087 // get the trade-in indicator in the form-wise 1088 boolean existTradeInIndicator = isTradeInIndicatorExistInAllLines(purApDocs); 1089 for (PurchasingAccountsPayableDocument purApDoc : purApDocs) { 1090 boolean existAdditionalCharge = false; 1091 // check if within the same document, exist both additional charge lines and normal line items. 1092 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 1093 existAdditionalCharge |= item.isAdditionalChargeNonTradeInIndicator(); 1094 } 1095 for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) { 1096 // when the indicator is not set yet... 1097 if (!item.isCreateAssetIndicator() || !item.isApplyPaymentIndicator()) { 1098 // If the lines on the purchase order are all additional charge lines, or trade-in allowance then we can apply 1099 // payment, or create asset. 1100 existTradeInIndicator = item.isItemAssignedToTradeInIndicator(); 1101 1102 if (item.isAdditionalChargeNonTradeInIndicator() || (item.isTradeInAllowance() && !existTradeInIndicator)) { 1103 item.setCreateAssetIndicator(true); 1104 item.setApplyPaymentIndicator(true); 1105 } 1106 // For line item(not additional charge line), if there is no pending additional charges and itself not a 1107 // trade-in indicator or it has trade-in indicator but no trade-in allowance, we allow apply payment or create 1108 // asset. 1109 else if (!existAdditionalCharge && (!item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && (!item.isItemAssignedToTradeInIndicator() || !existTradeInAllowance))) { 1110 item.setCreateAssetIndicator(true); 1111 item.setApplyPaymentIndicator(true); 1112 } 1113 } 1114 } 1115 } 1116 1117 } 1118 1119 1120 /** 1121 * Set item asset unit cost. 1122 * 1123 * @param item line item 1124 * @param totalCost total cost for this line item. 1125 */ 1126 protected void setItemAssetUnitCost(PurchasingAccountsPayableItemAsset item, KualiDecimal totalCost) { 1127 // set unit cost 1128 KualiDecimal quantity = item.getAccountsPayableItemQuantity(); 1129 if (quantity != null && quantity.isNonZero()) { 1130 item.setUnitCost(totalCost.divide(quantity)); 1131 } 1132 } 1133 1134 /** 1135 * Calculate item asset total cost 1136 * 1137 * @param item 1138 * @return line item total cost 1139 */ 1140 public KualiDecimal calculateItemAssetTotalCost(PurchasingAccountsPayableItemAsset item) { 1141 // Calculate and set total cost 1142 KualiDecimal totalCost = KualiDecimal.ZERO; 1143 for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) { 1144 if (account.getItemAccountTotalAmount() != null) { 1145 totalCost = totalCost.add(account.getItemAccountTotalAmount()); 1146 } 1147 } 1148 return totalCost; 1149 } 1150 1151 1152 /** 1153 * Gets the businessObjectService attribute. 1154 * 1155 * @return Returns the businessObjectService. 1156 */ 1157 public BusinessObjectService getBusinessObjectService() { 1158 return businessObjectService; 1159 } 1160 1161 1162 /** 1163 * Sets the businessObjectService attribute value. 1164 * 1165 * @param businessObjectService The businessObjectService to set. 1166 */ 1167 public void setBusinessObjectService(BusinessObjectService businessObjectService) { 1168 this.businessObjectService = businessObjectService; 1169 } 1170 1171 1172 /** 1173 * Gets the purApLineDao attribute. 1174 * 1175 * @return Returns the purApLineDao. 1176 */ 1177 public PurApLineDao getPurApLineDao() { 1178 return purApLineDao; 1179 } 1180 1181 1182 /** 1183 * Sets the purApLineDao attribute value. 1184 * 1185 * @param purApLineDao The purApLineDao to set. 1186 */ 1187 public void setPurApLineDao(PurApLineDao purApLineDao) { 1188 this.purApLineDao = purApLineDao; 1189 } 1190 1191 /** 1192 * Gets the purApInfoService attribute. 1193 * 1194 * @return Returns the purApInfoService. 1195 */ 1196 public PurApInfoService getPurApInfoService() { 1197 return purApInfoService; 1198 } 1199 1200 /** 1201 * Sets the purApInfoService attribute value. 1202 * 1203 * @param purApInfoService The purApInfoService to set. 1204 */ 1205 public void setPurApInfoService(PurApInfoService purApInfoService) { 1206 this.purApInfoService = purApInfoService; 1207 } 1208 1209 /** 1210 * get CAMS AssetService. 1211 * 1212 * @return 1213 */ 1214 protected AssetService getAssetService() { 1215 return SpringContext.getBean(AssetService.class); 1216 } 1217 1218 /** 1219 * Gets the capitalAssetManagementModuleService attribute. 1220 * 1221 * @return Returns the capitalAssetManagementModuleService. 1222 */ 1223 public CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() { 1224 return capitalAssetManagementModuleService; 1225 } 1226 1227 /** 1228 * Sets the capitalAssetManagementModuleService attribute value. 1229 * 1230 * @param capitalAssetManagementModuleService The capitalAssetManagementModuleService to set. 1231 */ 1232 public void setCapitalAssetManagementModuleService(CapitalAssetManagementModuleService capitalAssetManagementModuleService) { 1233 this.capitalAssetManagementModuleService = capitalAssetManagementModuleService; 1234 } 1235 1236 1237 }