/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.cab.document.service.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.ObjectCode;
import org.kuali.kfs.integration.cam.CapitalAssetManagementModuleService;
import org.kuali.kfs.module.cab.CabConstants;
import org.kuali.kfs.module.cab.CabPropertyConstants;
import org.kuali.kfs.module.cab.businessobject.GeneralLedgerEntry;
import org.kuali.kfs.module.cab.businessobject.Pretag;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableActionHistory;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset;
import org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableLineAssetAccount;
import org.kuali.kfs.module.cab.dataaccess.PurApLineDao;
import org.kuali.kfs.module.cab.document.service.PurApInfoService;
import org.kuali.kfs.module.cab.document.service.PurApLineService;
import org.kuali.kfs.module.cab.document.web.PurApLineSession;
import org.kuali.kfs.module.cam.CamsConstants;
import org.kuali.kfs.module.cam.document.service.AssetService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
/**
* This class provides default implementations of {@link PurApLineService}
*/
@Transactional
public class PurApLineServiceImpl implements PurApLineService {
private static final Logger LOG = Logger.getLogger(PurApLineServiceImpl.class);
private BusinessObjectService businessObjectService;
private PurApLineDao purApLineDao;
private PurApInfoService purApInfoService;
private CapitalAssetManagementModuleService capitalAssetManagementModuleService;
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#mergeLinesHasDifferentObjectSubTypes(java.util.List)
*/
@Override
public boolean mergeLinesHasDifferentObjectSubTypes(List<PurchasingAccountsPayableItemAsset> mergeLines) {
boolean invalid = false;
List<String> objectSubTypeList = new ArrayList<String>();
// collect all objectSubTypes from item lines
for (PurchasingAccountsPayableItemAsset itemAsset : mergeLines) {
for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT);
ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject();
if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) {
objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode());
}
}
}
// check if different objectSubTypes exist
if (!getAssetService().isObjectSubTypeCompatible(objectSubTypeList)) {
invalid = true;
}
return invalid;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#allocateLinesHasDifferentObjectSubTypes(java.util.List,
* org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset)
*/
@Override
public boolean allocateLinesHasDifferentObjectSubTypes(List<PurchasingAccountsPayableItemAsset> targetLines, PurchasingAccountsPayableItemAsset sourceLine) {
boolean invalid = false;
List<String> objectSubTypeList = new ArrayList<String>();
// collect objectSubTypes from target item lines
for (PurchasingAccountsPayableItemAsset itemAsset : targetLines) {
for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT);
ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject();
if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) {
objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode());
}
}
}
// collect objectSubTypes from source item line
if (ObjectUtils.isNotNull(sourceLine)) {
for (PurchasingAccountsPayableLineAssetAccount account : sourceLine.getPurchasingAccountsPayableLineAssetAccounts()) {
account.getGeneralLedgerEntry().refreshReferenceObject(CabPropertyConstants.GeneralLedgerEntry.FINANCIAL_OBJECT);
ObjectCode objCode = account.getGeneralLedgerEntry().getFinancialObject();
if (ObjectUtils.isNotNull(objCode) && StringUtils.isNotEmpty(objCode.getFinancialObjectSubTypeCode())) {
objectSubTypeList.add(objCode.getFinancialObjectSubTypeCode());
}
}
}
// check if different objectSubTypes exist
if (!getAssetService().isObjectSubTypeCompatible(objectSubTypeList)) {
invalid = true;
}
return invalid;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineDocumentService#inActivateDocument(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableDocument)
*/
@Override
public void conditionallyUpdateDocumentStatusAsProcessed(PurchasingAccountsPayableDocument selectedDoc) {
for (PurchasingAccountsPayableItemAsset item : selectedDoc.getPurchasingAccountsPayableItemAssets()) {
if (item.isActive()) {
return;
}
}
// set as processed when allocate/merge and all its items are in CAMs
selectedDoc.setActivityStatusCode(CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS);
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#resetSelectedValue(java.util.List)
*/
@Override
public void resetSelectedValue(List<PurchasingAccountsPayableDocument> purApDocs) {
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
item.setSelectedValue(false);
}
}
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#processAllocate(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset,
* java.util.List, org.kuali.kfs.module.cab.document.web.PurApLineSession, java.util.List)
*/
@Override
public boolean processAllocate(PurchasingAccountsPayableItemAsset allocateSourceLine,
List<PurchasingAccountsPayableItemAsset> allocateTargetLines,
List<PurchasingAccountsPayableActionHistory> actionsTakeHistory,
List<PurchasingAccountsPayableDocument> purApDocs,
boolean initiateFromBatch) {
boolean allocatedIndicator = true;
// indicator of additional charge allocation
boolean allocateAddlChrgIndicator = allocateSourceLine.isAdditionalChargeNonTradeInIndicator() | allocateSourceLine.isTradeInAllowance();
// Maintain this account List for update. So accounts already allocated won't take effect for account not allocated yet.
List<PurchasingAccountsPayableLineAssetAccount> newAccountList = new ArrayList<PurchasingAccountsPayableLineAssetAccount>();
// For each account in the source item, allocate it to the target items.
for (PurchasingAccountsPayableLineAssetAccount sourceAccount : allocateSourceLine.getPurchasingAccountsPayableLineAssetAccounts()) {
sourceAccount.refresh();
// Get allocate to target account list
List<PurchasingAccountsPayableLineAssetAccount> targetAccounts = getAllocateTargetAccounts(sourceAccount, allocateTargetLines, allocateAddlChrgIndicator);
if (!targetAccounts.isEmpty()) {
// Percentage amount to each target account
allocateByItemAccountAmount(sourceAccount, targetAccounts, newAccountList, actionsTakeHistory);
}
else {
allocatedIndicator = false;
break;
}
}
if (allocatedIndicator) {
postAllocateProcess(allocateSourceLine, allocateTargetLines, purApDocs, newAccountList, initiateFromBatch);
}
return allocatedIndicator;
}
/**
* Process after allocate.
*
* @param selectedLineItem
* @param allocateTargetLines
* @param purApDocs
* @param newAccountList
*/
protected void postAllocateProcess(PurchasingAccountsPayableItemAsset selectedLineItem, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, List<PurchasingAccountsPayableDocument> purApDocs, List<PurchasingAccountsPayableLineAssetAccount> newAccountList, boolean initiateFromBatch) {
// add new account into each item list
addNewAccountToItemList(newAccountList);
// update total cost and unit cost.
updateLineItemsCost(allocateTargetLines);
if (ObjectUtils.isNotNull(selectedLineItem.getPurchasingAccountsPayableDocument())) {
// remove allocate source line item.
selectedLineItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets().remove(selectedLineItem);
// when an line is removed from the document, we should check if it is the only active line in the document and
// in-activate document if yes.
conditionallyUpdateDocumentStatusAsProcessed(selectedLineItem.getPurchasingAccountsPayableDocument());
}
// Adjust create asset and apply payment indicator only when allocate additional charges.
if (!initiateFromBatch && (selectedLineItem.isAdditionalChargeNonTradeInIndicator() || selectedLineItem.isTradeInAllowance())) {
setAssetIndicator(purApDocs);
}
// update status code as user modified for allocate target lines
if (!initiateFromBatch) {
for (PurchasingAccountsPayableItemAsset allocateTargetItem : allocateTargetLines) {
updateItemStatusAsUserModified(allocateTargetItem);
}
}
}
/**
* Build removable asset lock map from the processedItems list. We need to remove all asset locks hold be items which has been
* merged or allocated to other lines.
*
* @param processedItems
* @return
*/
protected Map<String, Set> getRemovableAssetLocks(List<PurchasingAccountsPayableItemAsset> processedItems) {
Map<String, Set> removableAssetLocks = new HashMap<String, Set>();
for (PurchasingAccountsPayableItemAsset processedItem : processedItems) {
// For the time being, only for individual system, each item has its own asset numbers.
if (processedItem.getLockingInformation() != null && !CamsConstants.defaultLockingInformation.equals(processedItem.getLockingInformation())) {
addAssetLock(removableAssetLocks, processedItem);
}
else if (ObjectUtils.isNotNull(processedItem.getPurchasingAccountsPayableDocument())) {
// check other items if they are fully processed and can release the lock
List<PurchasingAccountsPayableItemAsset> remainingItems = processedItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets();
boolean fullyProcessed = true;
for (PurchasingAccountsPayableItemAsset itemAsset : remainingItems) {
if (!CabConstants.ActivityStatusCode.PROCESSED_IN_CAMS.equalsIgnoreCase(itemAsset.getActivityStatusCode())) {
fullyProcessed = false;
break;
}
}
if (fullyProcessed) {
// All the items are either merged or allocated to other document item. We should remove the asset lock
// retained by this document.
addAssetLock(removableAssetLocks, processedItem);
}
}
}
processedItems.clear();
return removableAssetLocks;
}
protected void addAssetLock(Map<String, Set> removableAssetLocks, PurchasingAccountsPayableItemAsset processedItem) {
if (processedItem.getLockingInformation() == null) {
processedItem.setLockingInformation(CamsConstants.defaultLockingInformation);
}
if (removableAssetLocks.containsKey(processedItem.getDocumentNumber())) {
Set lockingInfoList = removableAssetLocks.get(processedItem.getDocumentNumber());
lockingInfoList.add(processedItem.getLockingInformation());
}
else {
Set lockingInfoList = new HashSet<String>();
lockingInfoList.add(processedItem.getLockingInformation());
removableAssetLocks.put(processedItem.getDocumentNumber(), lockingInfoList);
}
}
/**
* Reset item total cost and unit cost for each item.
*
* @param allocateTargetLines
*/
protected void updateLineItemsCost(List<PurchasingAccountsPayableItemAsset> lineItems) {
// update target item unit cost and total cost
for (PurchasingAccountsPayableItemAsset item : lineItems) {
setLineItemCost(item);
}
}
/**
* update account list for each line item
*
* @param updatedAccountList
*/
protected void addNewAccountToItemList(List<PurchasingAccountsPayableLineAssetAccount> newAccountList) {
PurchasingAccountsPayableItemAsset lineItem = null;
for (PurchasingAccountsPayableLineAssetAccount newAccount : newAccountList) {
lineItem = newAccount.getPurchasingAccountsPayableItemAsset();
if (ObjectUtils.isNotNull(lineItem) && ObjectUtils.isNotNull(lineItem.getPurchasingAccountsPayableLineAssetAccounts())) {
lineItem.getPurchasingAccountsPayableLineAssetAccounts().add(newAccount);
}
}
}
/**
* Set line item total cost and unit cost.
*
* @param item
*/
protected void setLineItemCost(PurchasingAccountsPayableItemAsset item) {
KualiDecimal totalCost = calculateItemAssetTotalCost(item);
item.setTotalCost(totalCost);
setItemAssetUnitCost(item, totalCost);
}
/**
* Allocate one account line to target account lines percentage based on the account line amount.
*
* @param sourceAccount Account line to be allocated.
* @param targetAccounts Account lines which accept amount.
*/
protected void allocateByItemAccountAmount(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableLineAssetAccount> targetAccounts, List<PurchasingAccountsPayableLineAssetAccount> newAccountList, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory) {
KualiDecimal targetAccountsTotalAmount = KualiDecimal.ZERO;
KualiDecimal sourceAccountTotalAmount = sourceAccount.getItemAccountTotalAmount();
KualiDecimal amountAllocated = KualiDecimal.ZERO;
KualiDecimal additionalAmount = null;
// Calculate the targetAccountTotalAmount. Ignore the sign of the account amount when proportionally allocate based on
// account amount
for (PurchasingAccountsPayableLineAssetAccount targetAccount : targetAccounts) {
targetAccountsTotalAmount = targetAccountsTotalAmount.add(targetAccount.getItemAccountTotalAmount().abs());
}
for (Iterator<PurchasingAccountsPayableLineAssetAccount> iterator = targetAccounts.iterator(); iterator.hasNext();) {
PurchasingAccountsPayableLineAssetAccount targetAccount = iterator.next();
if (iterator.hasNext()) {
// Not working on the last node. Calculate additional charge amount by percentage. Ignore the sign of the account
// amount when proportionally allocate based on account amount
additionalAmount = targetAccount.getItemAccountTotalAmount().abs().multiply(sourceAccountTotalAmount).divide(targetAccountsTotalAmount);
amountAllocated = amountAllocated.add(additionalAmount);
}
else {
// Working on the last node, set the additional charge amount to the rest of sourceAccountTotalAmount.
additionalAmount = sourceAccountTotalAmount.subtract(amountAllocated);
}
// Code below mainly handle grouping account lines if they're from the same GL.
PurchasingAccountsPayableLineAssetAccount newAccount = getMatchingFromAccountList(targetAccounts, sourceAccount.getGeneralLedgerAccountIdentifier(), targetAccount);
if (newAccount != null) {
// If exists the same account line and GL entry, update its itemAccountTotalAmount. This account line could be other
// than targetAccount, but must belong to the same line item.
updateAccountAmount(additionalAmount, newAccount);
}
else {
// If exist account just created, grouping them and update the account amount.
newAccount = getMatchingFromAccountList(newAccountList, sourceAccount.getGeneralLedgerAccountIdentifier(), targetAccount);
if (newAccount != null) {
updateAccountAmount(additionalAmount, newAccount);
}
else {
// If the target account is a different GL entry, create a new account and attach to this line item.
newAccount = new PurchasingAccountsPayableLineAssetAccount(targetAccount.getPurchasingAccountsPayableItemAsset(), sourceAccount.getGeneralLedgerAccountIdentifier());
newAccount.setItemAccountTotalAmount(additionalAmount);
newAccount.setGeneralLedgerEntry(sourceAccount.getGeneralLedgerEntry());
newAccountList.add(newAccount);
}
}
// add to action history
addAllocateHistory(sourceAccount, actionsTakeHistory, additionalAmount, newAccount);
}
}
/**
* Save allocate action into session object.
*
* @param sourceAccount
* @param actionsTakeHistory
* @param additionalAmount
* @param newAccount
*/
protected void addAllocateHistory(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, KualiDecimal additionalAmount, PurchasingAccountsPayableLineAssetAccount newAccount) {
PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(sourceAccount.getPurchasingAccountsPayableItemAsset(), newAccount.getPurchasingAccountsPayableItemAsset(), CabConstants.Actions.ALLOCATE);
newAction.setGeneralLedgerAccountIdentifier(sourceAccount.getGeneralLedgerAccountIdentifier());
newAction.setItemAccountTotalAmount(additionalAmount);
newAction.setAccountsPayableItemQuantity(sourceAccount.getPurchasingAccountsPayableItemAsset().getAccountsPayableItemQuantity());
actionsTakeHistory.add(newAction);
}
/**
* Search matching account in targetAccounts by glIdentifier.
*
* @param targetAccounts
* @param glIdentifier
* @return
*/
protected PurchasingAccountsPayableLineAssetAccount getFromTargetAccountList(List<PurchasingAccountsPayableLineAssetAccount> targetAccounts, Long glIdentifier) {
for (PurchasingAccountsPayableLineAssetAccount account : targetAccounts) {
if (account.getGeneralLedgerAccountIdentifier().equals(glIdentifier)) {
return account;
}
}
return null;
}
/**
* Update targetAccount by additionalAmount.
*
* @param additionalAmount
* @param targetAccount
*/
protected void updateAccountAmount(KualiDecimal additionalAmount, PurchasingAccountsPayableLineAssetAccount targetAccount) {
KualiDecimal baseAmount = targetAccount.getItemAccountTotalAmount();
targetAccount.setItemAccountTotalAmount(baseAmount != null ? baseAmount.add(additionalAmount) : additionalAmount);
}
/**
* Searching in accountList by glIdentifier for matching account which associated with the same item as targetAccount.
*
* @param accountList
* @param glIdentifier
* @param targetAccount
* @return
*/
protected PurchasingAccountsPayableLineAssetAccount getMatchingFromAccountList(List<PurchasingAccountsPayableLineAssetAccount> accountList, Long glIdentifier, PurchasingAccountsPayableLineAssetAccount targetAccount) {
for (PurchasingAccountsPayableLineAssetAccount account : accountList) {
if (StringUtils.equalsIgnoreCase(targetAccount.getDocumentNumber(), account.getDocumentNumber()) && targetAccount.getAccountsPayableLineItemIdentifier().equals(account.getAccountsPayableLineItemIdentifier()) && targetAccount.getCapitalAssetBuilderLineNumber().equals(account.getCapitalAssetBuilderLineNumber()) && glIdentifier.equals(account.getGeneralLedgerAccountIdentifier())) {
return account;
}
}
return null;
}
/**
* Get the target account lines which will be used for allocate.
*
* @param sourceAccount
* @param lineItems
* @param addtionalCharge
* @return
*/
protected List<PurchasingAccountsPayableLineAssetAccount> getAllocateTargetAccounts(PurchasingAccountsPayableLineAssetAccount sourceAccount, List<PurchasingAccountsPayableItemAsset> allocateTargetLines, boolean addtionalCharge) {
GeneralLedgerEntry candidateEntry = null;
GeneralLedgerEntry sourceEntry = sourceAccount.getGeneralLedgerEntry();
List<PurchasingAccountsPayableLineAssetAccount> matchingAccounts = new ArrayList<PurchasingAccountsPayableLineAssetAccount>();
List<PurchasingAccountsPayableLineAssetAccount> allAccounts = new ArrayList<PurchasingAccountsPayableLineAssetAccount>();
// For additional charge allocation, target account selection is based on account lines with the same account number and
// object code. If no matching, select all account lines. For line item to line items, select all account lines as target.
for (PurchasingAccountsPayableItemAsset item : allocateTargetLines) {
for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
//KFSMI-5122: We need to refresh account general ledger entry so that the gl entries become visible as candidateEntry
account.refreshReferenceObject("generalLedgerEntry");
candidateEntry = account.getGeneralLedgerEntry();
if (ObjectUtils.isNotNull(candidateEntry)) {
// For additional charge, select matching account when account number and object code both match.
if (addtionalCharge && StringUtils.equalsIgnoreCase(sourceEntry.getChartOfAccountsCode(), candidateEntry.getChartOfAccountsCode()) && StringUtils.equalsIgnoreCase(sourceEntry.getAccountNumber(), candidateEntry.getAccountNumber()) && StringUtils.equalsIgnoreCase(sourceEntry.getFinancialObjectCode(), candidateEntry.getFinancialObjectCode())) {
matchingAccounts.add(account);
}
}
allAccounts.add(account);
}
}
return matchingAccounts.isEmpty() ? allAccounts : matchingAccounts;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#getAllocateTargetLines(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset,
* java.util.List)
*/
@Override
public List<PurchasingAccountsPayableItemAsset> getAllocateTargetLines(PurchasingAccountsPayableItemAsset selectedLineItem, List<PurchasingAccountsPayableDocument> purApDocs) {
List<PurchasingAccountsPayableItemAsset> targetLineItems = new ArrayList<PurchasingAccountsPayableItemAsset>();
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// If selected Line is additional charge line, get target lines from the same document.
// Else if selected Line is trade in allowance, target lines are item lines with TI indicator set.
// Otherwise, select items with check box set.
if (item.isActive() && item != selectedLineItem && ((selectedLineItem.isAdditionalChargeNonTradeInIndicator() && !item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && StringUtils.equalsIgnoreCase(selectedLineItem.getDocumentNumber(), item.getDocumentNumber())) || (selectedLineItem.isTradeInAllowance() && item.isItemAssignedToTradeInIndicator()) || (item.isSelectedValue()))) {
targetLineItems.add(item);
}
}
}
return targetLineItems;
}
/**
* Get selected merge lines. If this is merge all action, we need to manually append all additional charge lines since no select
* box associated with them.
*
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#getSelectedMergeLines(boolean, java.util.List)
*/
@Override
public List<PurchasingAccountsPayableItemAsset> getSelectedMergeLines(boolean isMergeAll, List<PurchasingAccountsPayableDocument> purApDocs) {
List<PurchasingAccountsPayableItemAsset> mergeLines = new ArrayList<PurchasingAccountsPayableItemAsset>();
boolean excludeTradeInAllowance = false;
// Handle one exception for merge all: when we have TI allowance but no TI indicator line, we should exclude
if (isMergeAll && !isTradeInIndicatorExistInAllLines(purApDocs) && isTradeInAllowanceExist(purApDocs)) {
excludeTradeInAllowance = true;
}
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// If not merge all action, select items are the merge lines. If it is merge all action, all lines should be
// candidate merge lines except trade-in allowance line when there is no trade-in indicator line.
if ((!isMergeAll && item.isSelectedValue()) || (isMergeAll && (!excludeTradeInAllowance || !item.isTradeInAllowance()))) {
mergeLines.add(item);
// setup non-persistent relationship from item to document.
item.setPurchasingAccountsPayableDocument(purApDoc);
}
}
}
return mergeLines;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isTradeInAllowanceExist(java.util.List)
*/
@Override
public boolean isTradeInAllowanceExist(List<PurchasingAccountsPayableDocument> purApDocs) {
boolean tradeInAllowance = false;
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// KFSCNTRB-1676/FSKD-5487
if (item.isTradeInAllowance() && item.isActive()) {
tradeInAllowance = true;
break;
}
}
}
return tradeInAllowance;
}
/**
* Check if TI indicator exists in all form lines
*
* @param purApDocs
* @return
*/
protected boolean isTradeInIndicatorExistInAllLines(List<PurchasingAccountsPayableDocument> purApDocs) {
boolean tradeInIndicator = false;
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// KFSCNTRB-1676/FSKD-5487
if (item.isItemAssignedToTradeInIndicator() && item.isActive()) {
tradeInIndicator = true;
break;
}
}
}
return tradeInIndicator;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isTradeInIndicatorExist(java.util.List)
*/
@Override
public boolean isTradeInIndExistInSelectedLines(List<PurchasingAccountsPayableItemAsset> itemAssets) {
boolean tradeInIndicator = false;
for (PurchasingAccountsPayableItemAsset item : itemAssets) {
if (item.isItemAssignedToTradeInIndicator()) {
tradeInIndicator = true;
break;
}
}
return tradeInIndicator;
}
/**
* If item assets are from the same document, we can ignore additional charges pending.
*
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isAdditionalChargePending(java.util.List)
*/
@Override
public boolean isAdditionalChargePending(List<PurchasingAccountsPayableItemAsset> itemAssets) {
boolean additionalChargePending = false;
Boolean diffDocment = false;
PurchasingAccountsPayableItemAsset firstAsset = itemAssets.get(0);
PurchasingAccountsPayableItemAsset lastAsset = itemAssets.get(itemAssets.size() - 1);
// Check if itemAssets are in different PurAp Document. itemAssets is a sorted list which has item assets from the same
// document grouping together.
if (ObjectUtils.isNotNull(firstAsset) && ObjectUtils.isNotNull(lastAsset) && !firstAsset.getDocumentNumber().equalsIgnoreCase(lastAsset.getDocumentNumber())) {
diffDocment = true;
}
// check if item assets from different document have additional charges not allocated yet. Bring all lines in
// the same document as checking candidate.
if (diffDocment) {
for (PurchasingAccountsPayableItemAsset item : itemAssets) {
if (ObjectUtils.isNotNull(item.getPurchasingAccountsPayableDocument())) {
for (PurchasingAccountsPayableItemAsset itemLine : item.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets()) {
if (itemLine.isAdditionalChargeNonTradeInIndicator()) {
additionalChargePending = true;
return additionalChargePending;
}
}
}
}
}
return additionalChargePending;
}
/**
* Check if the merge action is merge all.
*
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isMergeAllAction(java.util.List)
*/
@Override
public boolean isMergeAllAction(List<PurchasingAccountsPayableDocument> purApDocs) {
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// When there is one item line not selected, mergeAll is false
if (!item.isAdditionalChargeNonTradeInIndicator() && !item.isTradeInAllowance() && !item.isSelectedValue()) {
return false;
}
}
}
return true;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isAdditionalChargeExistInAllLines(java.util.List)
*/
@Override
public boolean isAdditionalChargeExistInAllLines(List<PurchasingAccountsPayableDocument> purApDocs) {
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// KFSCNTRB-1676/FSKD-5487
if (item.isAdditionalChargeNonTradeInIndicator() && item.isActive()) {
return true;
}
}
}
return false;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#processMerge(java.util.List)
*/
@Override
public void processMerge(List<PurchasingAccountsPayableItemAsset> mergeLines, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, boolean isMergeAll) {
PurchasingAccountsPayableItemAsset sourceItem = null;
PurchasingAccountsPayableLineAssetAccount targetAccount = null;
// use the first item and its accounts as the target
PurchasingAccountsPayableItemAsset firstItem = mergeLines.get(0);
List<PurchasingAccountsPayableLineAssetAccount> firstAccountList = firstItem.getPurchasingAccountsPayableLineAssetAccounts();
// Merge accounts starting from the second item to the last one.
for (int i = 1; i < mergeLines.size(); i++) {
sourceItem = mergeLines.get(i);
for (PurchasingAccountsPayableLineAssetAccount account : sourceItem.getPurchasingAccountsPayableLineAssetAccounts()) {
// Check if we can grouping the accounts. If yes, update the account amount without moving account line.
targetAccount = getFromTargetAccountList(firstAccountList, account.getGeneralLedgerAccountIdentifier());
if (targetAccount != null) {
updateAccountAmount(account.getItemAccountTotalAmount(), targetAccount);
}
else {
// move account from source to target
account.setDocumentNumber(firstItem.getDocumentNumber());
account.setAccountsPayableLineItemIdentifier(firstItem.getAccountsPayableLineItemIdentifier());
account.setCapitalAssetBuilderLineNumber(firstItem.getCapitalAssetBuilderLineNumber());
account.setPurchasingAccountsPayableItemAsset(firstItem);
firstAccountList.add(account);
}
}
}
// update action history, remove lines before merge and clean up the user input
postMergeProcess(mergeLines, actionsTakeHistory, isMergeAll);
}
/**
* Process after merge.
*
* @param mergeLines
* @param purApLineSession
* @param isMergeAll
*/
protected void postMergeProcess(List<PurchasingAccountsPayableItemAsset> mergeLines, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory, boolean isMergeAll) {
String actionTypeCode = isMergeAll ? CabConstants.Actions.MERGE_ALL : CabConstants.Actions.MERGE;
PurchasingAccountsPayableItemAsset targetItem = mergeLines.get(0);
// set unit cost and total cost
setLineItemCost(targetItem);
targetItem.setItemAssignedToTradeInIndicator(false);
// For all merge source lines(the first line is considered as target technically), remove it from the document and update in
// the action history.
for (int i = 1; i < mergeLines.size(); i++) {
PurchasingAccountsPayableItemAsset sourceItem = mergeLines.get(i);
// Update the action history.
addMergeHistory(actionsTakeHistory, actionTypeCode, sourceItem, targetItem);
if (ObjectUtils.isNotNull(sourceItem.getPurchasingAccountsPayableDocument())) {
// remove mergeLines from the document
sourceItem.getPurchasingAccountsPayableDocument().getPurchasingAccountsPayableItemAssets().remove(sourceItem);
// if all active lines are merged to other line, we need to in-activate the current document
conditionallyUpdateDocumentStatusAsProcessed(sourceItem.getPurchasingAccountsPayableDocument());
}
}
// set the target item itemLineNumber for pre-tagging
Integer poId = targetItem.getPurchasingAccountsPayableDocument().getPurchaseOrderIdentifier();
if (poId != null) {
Pretag targetPretag = getTargetPretag(mergeLines, poId);
if (targetPretag != null) {
targetItem.setItemLineNumber(targetPretag.getItemLineNumber());
}
}
// update create asset/ apply payment indicator if any of the merged lines has the indicator set.
updateAssetIndicatorAfterMerge(mergeLines);
// update activity status code as modified
updateItemStatusAsUserModified(targetItem);
}
/**
* Update create asset and apply payment indicators after merge.
*
* @param mergeLines
*/
protected void updateAssetIndicatorAfterMerge(List<PurchasingAccountsPayableItemAsset> mergeLines) {
boolean existCreateAsset = false;
boolean existApplyPayment = false;
PurchasingAccountsPayableItemAsset targetItem = mergeLines.get(0);
// set indicator if any of the source item set it or target item set it.
for (int i = 1; i < mergeLines.size(); i++) {
PurchasingAccountsPayableItemAsset sourceItem = mergeLines.get(i);
existCreateAsset |= sourceItem.isCreateAssetIndicator();
existApplyPayment |= sourceItem.isApplyPaymentIndicator();
}
targetItem.setCreateAssetIndicator(targetItem.isCreateAssetIndicator() | existCreateAsset);
targetItem.setApplyPaymentIndicator(targetItem.isApplyPaymentIndicator() | existApplyPayment);
}
/**
* Get the first pre-tag for given itemLines
*
* @param itemLines
* @param purchaseOrderIdentifier
* @return
*/
protected Pretag getTargetPretag(List<PurchasingAccountsPayableItemAsset> itemLines, Integer purchaseOrderIdentifier) {
for (PurchasingAccountsPayableItemAsset item : itemLines) {
Pretag newTag = getPreTagLineItem(purchaseOrderIdentifier, item.getItemLineNumber());
if (isPretaggingExisting(newTag)) {
return newTag;
}
}
return null;
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isPretaggingExisting(org.kuali.kfs.module.cab.businessobject.Pretag)
*/
@Override
public boolean isPretaggingExisting(Pretag newTag) {
return ObjectUtils.isNotNull(newTag) && newTag.getPretagDetails() != null && !newTag.getPretagDetails().isEmpty();
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#isMultipleTagExisting(java.lang.Integer, java.util.Set)
*/
@Override
public boolean isMultipleTagExisting(Integer purchaseOrderIdentifier, Set<Integer> itemLineNumbers) {
Pretag firstTag = null;
for (Iterator iterator = itemLineNumbers.iterator(); iterator.hasNext();) {
Integer itemLineNumber = (Integer) iterator.next();
Pretag newTag = getPreTagLineItem(purchaseOrderIdentifier, itemLineNumber);
if (isPretaggingExisting(newTag)) {
if (firstTag != null) {
// find the second preTagging item
return true;
}
else {
firstTag = newTag;
}
}
}
return false;
}
/**
* Add merge action to the action history.
*
* @param purApLineSession
* @param isMergeAllAction
* @param firstItem
* @param item
*/
protected void addMergeHistory(List<PurchasingAccountsPayableActionHistory> actionsTakenHistory, String actionTypeCode, PurchasingAccountsPayableItemAsset sourceItem, PurchasingAccountsPayableItemAsset targetItem) {
// create action history records for each account from the source lines.
for (PurchasingAccountsPayableLineAssetAccount sourceAccount : sourceItem.getPurchasingAccountsPayableLineAssetAccounts()) {
PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(sourceItem, targetItem, actionTypeCode);
newAction.setAccountsPayableItemQuantity(sourceItem.getAccountsPayableItemQuantity());
newAction.setItemAccountTotalAmount(sourceAccount.getItemAccountTotalAmount());
newAction.setGeneralLedgerAccountIdentifier(sourceAccount.getGeneralLedgerAccountIdentifier());
actionsTakenHistory.add(newAction);
}
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#processPercentPayment(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset)
*/
@Override
public void processPercentPayment(PurchasingAccountsPayableItemAsset itemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakenHistory) {
KualiDecimal oldQty = itemAsset.getAccountsPayableItemQuantity();
KualiDecimal newQty = new KualiDecimal(1);
// update quantity, total cost and unit cost.
if (oldQty.isLessThan(newQty)) {
itemAsset.setAccountsPayableItemQuantity(newQty);
setLineItemCost(itemAsset);
// add to action history
addPercentPaymentHistory(actionsTakenHistory, itemAsset, oldQty);
// update status code
updateItemStatusAsUserModified(itemAsset);
}
}
/**
* Updates activity status code when percent payment/split/allocate/merge action taken.
* @param itemAsset itemAsset for which action status is to be modified
*/
protected void updateItemStatusAsUserModified(PurchasingAccountsPayableItemAsset itemAsset) {
itemAsset.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
for (PurchasingAccountsPayableLineAssetAccount account : itemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
account.setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
}
itemAsset.getPurchasingAccountsPayableDocument().setActivityStatusCode(CabConstants.ActivityStatusCode.MODIFIED);
}
/**
* Update action history for the percent payment action.
*
* @param actionsTaken
* @param item
* @param oldQty
*/
protected void addPercentPaymentHistory(List<PurchasingAccountsPayableActionHistory> actionsTakenHistory, PurchasingAccountsPayableItemAsset item, KualiDecimal oldQty) {
// create and set up one action history record for this action
PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(item, item, CabConstants.Actions.PERCENT_PAYMENT);
// record quantity before percent payment into action history
newAction.setAccountsPayableItemQuantity(oldQty);
actionsTakenHistory.add(newAction);
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#processSplit(org.kuali.kfs.module.cab.businessobject.PurchasingAccountsPayableItemAsset)
*/
@Override
public void processSplit(PurchasingAccountsPayableItemAsset splitItemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakeHistory) {
PurchasingAccountsPayableDocument purApDoc = splitItemAsset.getPurchasingAccountsPayableDocument();
// update activity status code for split item. it will be propogated to new created item and its accounts.
updateItemStatusAsUserModified(splitItemAsset);
// create a new item asset from the current item asset.
PurchasingAccountsPayableItemAsset newItemAsset = new PurchasingAccountsPayableItemAsset(splitItemAsset);
// set cab line number
newItemAsset.setCapitalAssetBuilderLineNumber(getMaxCabLineNumber(splitItemAsset, purApDoc) + 1);
newItemAsset.setAccountsPayableItemQuantity(splitItemAsset.getSplitQty());
// Set account list for new item asset and update current account amount value.
createAccountsForNewItemAsset(splitItemAsset, newItemAsset);
// set unit cost and total cost in new item
setLineItemCost(newItemAsset);
// Adjust current item asset quantity, total cost and unit cost
splitItemAsset.setAccountsPayableItemQuantity(splitItemAsset.getAccountsPayableItemQuantity().subtract(splitItemAsset.getSplitQty()));
setLineItemCost(splitItemAsset);
// add the new item into document and sort.
purApDoc.getPurchasingAccountsPayableItemAssets().add(newItemAsset);
Collections.sort(purApDoc.getPurchasingAccountsPayableItemAssets());
// Add to action history
addSplitHistory(splitItemAsset, newItemAsset, actionsTakeHistory);
// clear up user input
splitItemAsset.setSplitQty(null);
}
/**
* Get the max cab line #. As part of the primary key, it should be the max value among the form item list and DB.
*
* @param splitItemAsset
* @param purApDoc
* @return
*/
protected int getMaxCabLineNumber(PurchasingAccountsPayableItemAsset splitItemAsset, PurchasingAccountsPayableDocument purApDoc) {
// get the max CAB line number in DB.
Integer maxDBCabLineNbr = purApLineDao.getMaxCabLineNumber(splitItemAsset.getDocumentNumber(), splitItemAsset.getAccountsPayableLineItemIdentifier());
// get the max CAB line number in form.
int availableCabLineNbr = getMaxCabLineNbrForItemInForm(purApDoc, splitItemAsset);
if (maxDBCabLineNbr.intValue() > availableCabLineNbr) {
availableCabLineNbr = maxDBCabLineNbr.intValue();
}
return availableCabLineNbr;
}
/**
* Search the current active items and return the max CAB line # for split new item .
*
* @param purApDoc
* @param currentItemAsset
* @return
*/
protected int getMaxCabLineNbrForItemInForm(PurchasingAccountsPayableDocument purApDoc, PurchasingAccountsPayableItemAsset currentItemAsset) {
int maxCabLineNbr = 0;
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
if (item.getDocumentNumber().equalsIgnoreCase(currentItemAsset.getDocumentNumber()) && item.getAccountsPayableLineItemIdentifier().equals(currentItemAsset.getAccountsPayableLineItemIdentifier()) && item.getCapitalAssetBuilderLineNumber().intValue() > maxCabLineNbr) {
maxCabLineNbr = item.getCapitalAssetBuilderLineNumber().intValue();
}
}
return maxCabLineNbr;
}
/**
* Update action history for a split action.
*
* @param currentItemAsset
* @param newItemAsset
* @param actionsTaken
*/
protected void addSplitHistory(PurchasingAccountsPayableItemAsset currentItemAsset, PurchasingAccountsPayableItemAsset newItemAsset, List<PurchasingAccountsPayableActionHistory> actionsTakenHistory) {
// for each account moved from original item to new item, create one action history record
for (PurchasingAccountsPayableLineAssetAccount account : newItemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
PurchasingAccountsPayableActionHistory newAction = new PurchasingAccountsPayableActionHistory(currentItemAsset, newItemAsset, CabConstants.Actions.SPLIT);
newAction.setGeneralLedgerAccountIdentifier(account.getGeneralLedgerAccountIdentifier());
// quantity moved from original item to new item
newAction.setAccountsPayableItemQuantity(newItemAsset.getAccountsPayableItemQuantity());
// account amount moved to new item
newAction.setItemAccountTotalAmount(account.getItemAccountTotalAmount());
actionsTakenHistory.add(newAction);
}
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#processSaveBusinessObjects(java.util.List,
* org.kuali.kfs.module.cab.document.web.PurApLineSession)
*/
@Override
public void processSaveBusinessObjects(List<PurchasingAccountsPayableDocument> purApDocs, PurApLineSession purApLineSession) {
// Get removable asset locks which could be generated by allocate or merge when items removed and the lock should be
// released.
Map<String, Set> removableAssetLocks = getRemovableAssetLocks(purApLineSession.getProcessedItems());
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
// auto save items(including deleted items) and accounts due to auto-update setting in OJB.
businessObjectService.save(purApDoc);
}
// remove asset locks
for (String lockingDocumentNbr : removableAssetLocks.keySet()) {
Set<String> lockingInfoList = removableAssetLocks.get(lockingDocumentNbr);
for (String lockingInfo : lockingInfoList) {
if (this.getCapitalAssetManagementModuleService().isAssetLockedByCurrentDocument(lockingDocumentNbr, lockingInfo)) {
this.getCapitalAssetManagementModuleService().deleteAssetLocks(lockingDocumentNbr, lockingInfo);
}
}
}
if (purApLineSession != null) {
// save to action history table
List<PurchasingAccountsPayableActionHistory> historyList = purApLineSession.getActionsTakenHistory();
if (historyList != null && !historyList.isEmpty()) {
businessObjectService.save(historyList);
historyList.clear();
}
// save to generalLedgerEntry table
List<GeneralLedgerEntry> glUpdateList = purApLineSession.getGlEntryUpdateList();
if (glUpdateList != null && !glUpdateList.isEmpty()) {
businessObjectService.save(glUpdateList);
glUpdateList.clear();
}
}
}
/**
* Create asset account list for new item asset and update the current account amount.
*
* @param oldItemAsset old line item.
* @param newItemAsset new line item.
*/
protected void createAccountsForNewItemAsset(PurchasingAccountsPayableItemAsset currentItemAsset, PurchasingAccountsPayableItemAsset newItemAsset) {
KualiDecimal currentQty = currentItemAsset.getAccountsPayableItemQuantity();
KualiDecimal splitQty = currentItemAsset.getSplitQty();
List<PurchasingAccountsPayableLineAssetAccount> newAccountsList = newItemAsset.getPurchasingAccountsPayableLineAssetAccounts();
PurchasingAccountsPayableLineAssetAccount newAccount;
for (PurchasingAccountsPayableLineAssetAccount currentAccount : currentItemAsset.getPurchasingAccountsPayableLineAssetAccounts()) {
// create accounts for new item asset.
newAccount = new PurchasingAccountsPayableLineAssetAccount(newItemAsset, currentAccount.getGeneralLedgerAccountIdentifier());
newAccount.setItemAccountTotalAmount(currentAccount.getItemAccountTotalAmount().multiply(splitQty).divide(currentQty));
newAccount.setGeneralLedgerEntry(currentAccount.getGeneralLedgerEntry());
newAccountsList.add(newAccount);
// Adjust current account amount for split item( subtract new account amount for original amount)
currentAccount.setItemAccountTotalAmount(currentAccount.getItemAccountTotalAmount().subtract(newAccount.getItemAccountTotalAmount()));
}
}
/**
* Set object code by the first one from the accounting lines.
*
* @param item Selected line item.
*/
protected void setFirstFinancialObjectCode(PurchasingAccountsPayableItemAsset item) {
String firstFinancialObjectCode = null;
for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
if (ObjectUtils.isNotNull(account.getGeneralLedgerEntry())) {
firstFinancialObjectCode = account.getGeneralLedgerEntry().getFinancialObjectCode();
break;
}
}
item.setFirstFincialObjectCode(firstFinancialObjectCode);
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#buildPurApItemAssetList(java.util.List)
*/
@Override
public void buildPurApItemAssetList(List<PurchasingAccountsPayableDocument> purApDocs) {
for (PurchasingAccountsPayableDocument purApDoc : purApDocs) {
for (PurchasingAccountsPayableItemAsset item : purApDoc.getPurchasingAccountsPayableItemAssets()) {
// set item non-persistent fields from PurAp PREQ/CM item tables
purApInfoService.setAccountsPayableItemsFromPurAp(item, purApDoc.getDocumentTypeCode());
// set line item unit cost and total cost
setLineItemCost(item);
// set financial object code
setFirstFinancialObjectCode(item);
// KFSMI-5337
// Adding the following code to populate item description from PreAsset tagging
updateAssetDescriptionFromPreTag(item, purApDoc);
}
// For display purpose, move additional charges including trade-in below item lines.
Collections.sort(purApDoc.getPurchasingAccountsPayableItemAssets());
}
// set CAMS Transaction type from PurAp
purApInfoService.setCamsTransactionFromPurAp(purApDocs);
// set create asset/apply payment indicator which are used to control display two buttons.
setAssetIndicator(purApDocs);
}
private void updateAssetDescriptionFromPreTag(PurchasingAccountsPayableItemAsset item, PurchasingAccountsPayableDocument purApDoc){
Pretag preTag = null;
if (item.isActive()) {
preTag = getPreTagLineItem(purApDoc.getPurchaseOrderIdentifier(), item.getItemLineNumber());
if (ObjectUtils.isNotNull(preTag) && StringUtils.isNotBlank(preTag.getAssetTopsDescription())) {
item.setAccountsPayableLineItemDescription(preTag.getAssetTopsDescription());
}
}
}
/**
* @see org.kuali.kfs.module.cab.document.service.PurApLineService#getPreTagLineItem(java.lang.String, java.lang.Integer)
*/
@Override
public Pretag getPreTagLineItem(Integer purchaseOrderIdentifier, Integer lineItemNumber) {
if (purchaseOrderIdentifier == null || lineItemNumber == null) {
return null;
}
Map<String, Object> pKeys = new HashMap<String, Object>();
pKeys.put(CabPropertyConstants.Pretag.PURCHASE_ORDER_NUMBER, purchaseOrderIdentifier);
pKeys.put(CabPropertyConstants.Pretag.ITEM_LINE_NUMBER, lineItemNumber);
return businessObjectService.findByPrimaryKey(Pretag.class, pKeys);
}
/**
* KFSCNTRB-1676/FSKD-5487
* Sets create asset and apply payment indicators. These two indicators are referenced by jsp to control display of these two
* action links. How to set these two indicators is based on the business rules. We need to put the following situations into
* consideration. Since we move allocate additional charge allocation to CAB batch, we bring over additional charge lines only
* when they are the only items in the document, or they are trade-in allowances, or they are from cancelled AP docs or FO changes.
* To accommodate this, we relax the rules and defined as:
* 1. Throughout the AP document list, if there're both unallocated TRDI additional charges and active trade-in ITEM lines,
* then disable the process action links on these lines across the document list;
* 2. Within each AP document in the list, if there're both unallocated non-TRDI additional charges and active ITEM lines,
* then disable the process action links on all lines in this document;
* 3. Except for above cases, display action links for other lines.
* NOTE: Above, AP document list refer to all active PREQs or CMs extracted into CAB for the same PO; they are processed on the same CAB screen.
*
* @param apDocs AP document list containing all active PREQs or CMs extracted into CAB for the same PO.
*/
protected void setAssetIndicator(List<PurchasingAccountsPayableDocument> apDocs) {
/* For un-allocated TRDI and active trade-in ITEM lines, we need to check throughout the entire active AP documents list,
* because a TRDI line can only be allocated to trade-in ITEM lines, which could exist in other AP documents in the list.
* Consider the following scenario: some PREQ with only trade-in ITEMs but no TRDI lines was extracted and submitted first
* and become inactive; later another PREQ with only TRDI lines but no trade-in ITEMs for the same PO are created and extracted.
* At this point, the later PREQ would be stuck if we force allocation, because the TRDI lines have no target trade-in ITEM lines
* to allocate to, while the ITEM lines are waiting for allocation before they can be processed. To resolve this issue,
* first of all, we should prevent trade-in ITEMs to be submitted before TRDI lines are allocated across all active documents;
* further more, if the undesirable scenario above happens, we have to relax the rule of forcing allocation, i.e.
* in order to proceed, we need to allow both the TRDI and the ITEM lines to be processed without allocation.
*/
boolean existUnallocatedAdditionalTRDI = existUnallocatedAdditionalTRDI(apDocs);
boolean existActiveItemTradeIn = existActiveItemTradeIn(apDocs);
for (PurchasingAccountsPayableDocument apDoc : apDocs) {
/* For non-TRDI additional charges pending allocation, we should check within each document instead of across the document list,
* because non-TRDI can allocate to any active ITEM within the same document, no need to worry about the stuck scenario.
* Meanwhile we don't want to prevent one document from processing just because some other document has non-TRDI pending allocation.
*/
boolean existUnallocatedAdditionalNonTRDI = existUnallocatedAdditionalNonTRDI(apDoc);
boolean existActiveItemLines = existActiveItemLines(apDoc);
// if within the AP doc, there're both unallocated non-TRDI additional charge and active ITEM lines,
// we need to disable process actions on all lines to force allocation first
if (existUnallocatedAdditionalNonTRDI && existActiveItemLines) {
continue;
}
// otherwise, enable/disable process actions in the document based on the allocation status of TRDI/trade-in lines across the document list
for (PurchasingAccountsPayableItemAsset item : apDoc.getPurchasingAccountsPayableItemAssets()) {
// skip inactive lines
if (!item.isActive()) {
continue;
}
// if we are NOT in the situation where there're both unallocated TRDI additional charge and active trade-in ITEM lines
// throughout the AP document list, then we can enable process actions on all active lines
if (!(existUnallocatedAdditionalTRDI && existActiveItemTradeIn)) {
item.setCreateAssetIndicator(true);
item.setApplyPaymentIndicator(true);
}
// otherwise, disable process actions only on the unallocated TRDI additional charge and active trade-in ITEM lines,
// while enable the actions on all other active lines in the AP document.
// Note: Since allocated lines are removed from the list, all remaining active lines are unallocated.
else if (!item.isActiveAdditionalTRDI() && !item.isActiveItemTradeIn()) {
item.setCreateAssetIndicator(true);
item.setApplyPaymentIndicator(true);
}
}
}
}
/**
* KFSCNTRB-1676/FSKD-5487
* Checks whether there exists any unallocated TRDI additional charge asset line throughout the AP document list.
* @param apDocs AP document list containing all active PREQs/CMs extracted into CAB for the same PO.
*/
protected boolean existUnallocatedAdditionalTRDI(List<PurchasingAccountsPayableDocument> apDocs) {
for (PurchasingAccountsPayableDocument apDoc : apDocs) {
for (PurchasingAccountsPayableItemAsset item : apDoc.getPurchasingAccountsPayableItemAssets()) {
// Each time an asset line is allocated it will be removed from the list, so we can assume that all remaining lines in the doc
// are unallocated; thus we don't need to further distinguish the action status code except that the line is active.
if (item.isActiveAdditionalTRDI() ) {
return true;
}
}
}
return false;
}
/**
* KFSCNTRB-1676/FSKD-5487
* Checks whether there exists any unallocated non-TRDI additional charge asset line in the specified AP document.
* @param apDocs the specified PREQ/CM document.
*/
protected boolean existUnallocatedAdditionalNonTRDI(PurchasingAccountsPayableDocument apDoc) {
for (PurchasingAccountsPayableItemAsset item : apDoc.getPurchasingAccountsPayableItemAssets()) {
// Each time an asset line is allocated it will be removed from the list, so we can assume that all remaining lines in the doc
// are unallocated; thus we don't need to further distinguish the action status code except that the line is active.
if (item.isActiveAdditionalNonTRDI()) {
return true;
}
}
return false;
}
/**
* KFSCNTRB-1676/FSKD-5487
* Checks whether there exists any active trade-in ITEM asset line throughout the AP document list.
* @param apDocs AP document list containing all active PREQs/CMs extracted into CAB for the same PO.
*/
protected boolean existActiveItemTradeIn(List<PurchasingAccountsPayableDocument> apDocs) {
for (PurchasingAccountsPayableDocument apDoc : apDocs) {
for (PurchasingAccountsPayableItemAsset item : apDoc.getPurchasingAccountsPayableItemAssets()) {
if (item.isActiveItemTradeIn()) {
return true;
}
}
}
return false;
}
/**
* KFSCNTRB-1676/FSKD-5487
* Checks whether there exists any active ITEM asset line in the specified AP document.
* @param apDocs the specified PREQ/CM document.
*/
protected boolean existActiveItemLines(PurchasingAccountsPayableDocument apDoc) {
for (PurchasingAccountsPayableItemAsset item : apDoc.getPurchasingAccountsPayableItemAssets()) {
if (item.isActiveItemLine()) {
return true;
}
}
return false;
}
/**
* Set item asset unit cost.
*
* @param item line item
* @param totalCost total cost for this line item.
*/
protected void setItemAssetUnitCost(PurchasingAccountsPayableItemAsset item, KualiDecimal totalCost) {
// set unit cost
KualiDecimal quantity = item.getAccountsPayableItemQuantity();
if (quantity != null && quantity.isNonZero()) {
item.setUnitCost(totalCost.divide(quantity));
}
}
/**
* Calculate item asset total cost
*
* @param item
* @return line item total cost
*/
public KualiDecimal calculateItemAssetTotalCost(PurchasingAccountsPayableItemAsset item) {
// Calculate and set total cost
KualiDecimal totalCost = KualiDecimal.ZERO;
for (PurchasingAccountsPayableLineAssetAccount account : item.getPurchasingAccountsPayableLineAssetAccounts()) {
if (account.getItemAccountTotalAmount() != null) {
totalCost = totalCost.add(account.getItemAccountTotalAmount());
}
}
return totalCost;
}
/**
* Gets the businessObjectService attribute.
*
* @return Returns the businessObjectService.
*/
public BusinessObjectService getBusinessObjectService() {
return businessObjectService;
}
/**
* Sets the businessObjectService attribute value.
*
* @param businessObjectService The businessObjectService to set.
*/
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
/**
* Gets the purApLineDao attribute.
*
* @return Returns the purApLineDao.
*/
public PurApLineDao getPurApLineDao() {
return purApLineDao;
}
/**
* Sets the purApLineDao attribute value.
*
* @param purApLineDao The purApLineDao to set.
*/
public void setPurApLineDao(PurApLineDao purApLineDao) {
this.purApLineDao = purApLineDao;
}
/**
* Gets the purApInfoService attribute.
*
* @return Returns the purApInfoService.
*/
public PurApInfoService getPurApInfoService() {
return purApInfoService;
}
/**
* Sets the purApInfoService attribute value.
*
* @param purApInfoService The purApInfoService to set.
*/
public void setPurApInfoService(PurApInfoService purApInfoService) {
this.purApInfoService = purApInfoService;
}
/**
* get CAMS AssetService.
*
* @return
*/
protected AssetService getAssetService() {
return SpringContext.getBean(AssetService.class);
}
/**
* Gets the capitalAssetManagementModuleService attribute.
*
* @return Returns the capitalAssetManagementModuleService.
*/
public CapitalAssetManagementModuleService getCapitalAssetManagementModuleService() {
return capitalAssetManagementModuleService;
}
/**
* Sets the capitalAssetManagementModuleService attribute value.
*
* @param capitalAssetManagementModuleService The capitalAssetManagementModuleService to set.
*/
public void setCapitalAssetManagementModuleService(CapitalAssetManagementModuleService capitalAssetManagementModuleService) {
this.capitalAssetManagementModuleService = capitalAssetManagementModuleService;
}
}