use of org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase in project cu-kfs by CU-CommunityApps.
the class CuPurapServiceImpl method prorateForTradeInAndFullOrderDiscount.
public void prorateForTradeInAndFullOrderDiscount(PurchasingAccountsPayableDocument purDoc) {
if (purDoc instanceof VendorCreditMemoDocument) {
throw new RuntimeException("This method not applicable for VCM documents");
}
// TODO: are we throwing sufficient errors in this method?
PurApItem fullOrderDiscount = null;
PurApItem tradeIn = null;
KualiDecimal totalAmount = KualiDecimal.ZERO;
KualiDecimal totalTaxAmount = KualiDecimal.ZERO;
List<PurApAccountingLine> distributedAccounts = null;
List<SourceAccountingLine> summaryAccounts = null;
// iterate through below the line and grab FoD and TrdIn.
for (PurApItem item : purDoc.getItems()) {
if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) {
fullOrderDiscount = item;
} else if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE)) {
tradeIn = item;
}
}
// If Discount is not null or zero get proration list for all non misc items and set (if not empty?)
if (fullOrderDiscount != null && fullOrderDiscount.getExtendedPrice() != null && fullOrderDiscount.getExtendedPrice().isNonZero()) {
// empty
KNSGlobalVariables.getMessageList().add("Full order discount accounts cleared and regenerated");
fullOrderDiscount.getSourceAccountingLines().clear();
// total amount is pretax dollars
totalAmount = purDoc.getTotalDollarAmountAboveLineItems().subtract(purDoc.getTotalTaxAmountAboveLineItems());
totalTaxAmount = purDoc.getTotalTaxAmountAboveLineItems();
// Before we generate account summary, we should update the account amounts first.
purapAccountingService.updateAccountAmounts(purDoc);
// calculate tax
boolean salesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND);
boolean useTaxIndicator = purDoc.isUseTaxIndicator();
if (salesTaxInd == true && (ObjectUtils.isNull(fullOrderDiscount.getItemTaxAmount()) && useTaxIndicator == false)) {
KualiDecimal discountAmount = fullOrderDiscount.getExtendedPrice();
KualiDecimal discountTaxAmount = discountAmount.divide(totalAmount).multiply(totalTaxAmount);
fullOrderDiscount.setItemTaxAmount(discountTaxAmount);
}
// generate summary
summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils.getAboveTheLineOnly(purDoc.getItems()));
if (summaryAccounts.size() == 0) {
if (purDoc.shouldGiveErrorForEmptyAccountsProration()) {
GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "full order discount");
}
} else {
// prorate accounts
distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount.add(totalTaxAmount), 2, fullOrderDiscount.getAccountingLineClass());
for (PurApAccountingLine distributedAccount : distributedAccounts) {
// KFSPTS-2200 : set item, so it can be verified as discount when validating
if (distributedAccount instanceof PurApAccountingLineBase) {
((PurApAccountingLineBase) distributedAccount).setDiscountTradeIn(true);
}
BigDecimal percent = distributedAccount.getAccountLinePercent();
BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue()));
distributedAccount.setAccountLinePercent(roundedPercent);
}
// update amounts on distributed accounts
purapAccountingService.updateAccountAmountsWithTotal(distributedAccounts, totalAmount, fullOrderDiscount.getTotalAmount());
fullOrderDiscount.setSourceAccountingLines(distributedAccounts);
}
} else if (fullOrderDiscount != null && (fullOrderDiscount.getExtendedPrice() == null || fullOrderDiscount.getExtendedPrice().isZero())) {
fullOrderDiscount.getSourceAccountingLines().clear();
}
// If tradeIn is not null or zero get proration list for all non misc items and set (if not empty?)
if (tradeIn != null && tradeIn.getExtendedPrice() != null && tradeIn.getExtendedPrice().isNonZero()) {
tradeIn.getSourceAccountingLines().clear();
totalAmount = purDoc.getTotalDollarAmountForTradeIn();
KualiDecimal tradeInTotalAmount = tradeIn.getTotalAmount();
// Before we generate account summary, we should update the account amounts first.
purapAccountingService.updateAccountAmounts(purDoc);
// Before generating the summary, lets replace the object code in a cloned accounts collection sothat we can
// consolidate all the modified object codes during summary generation.
List<PurApItem> clonedTradeInItems = new ArrayList<PurApItem>();
Collection<String> objectSubTypesRequiringQty = new ArrayList<String>(SpringContext.getBean(ParameterService.class).getParameterValuesAsString(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.OBJECT_SUB_TYPES_REQUIRING_QUANTITY));
Collection<String> purchasingObjectSubTypes = new ArrayList<String>(SpringContext.getBean(ParameterService.class).getParameterValuesAsString(KfsParameterConstants.CAPITAL_ASSETS_BATCH.class, PurapParameterConstants.PURCHASING_OBJECT_SUB_TYPES));
String tradeInCapitalObjectCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_ASSET");
String tradeInCapitalLeaseObjCd = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_LEASE");
for (PurApItem item : purDoc.getTradeInItems()) {
PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(item);
List<PurApAccountingLine> sourceAccountingLines = cloneItem.getSourceAccountingLines();
for (PurApAccountingLine accountingLine : sourceAccountingLines) {
if (objectSubTypesRequiringQty.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())) {
accountingLine.setFinancialObjectCode(tradeInCapitalObjectCode);
} else if (purchasingObjectSubTypes.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())) {
accountingLine.setFinancialObjectCode(tradeInCapitalLeaseObjCd);
}
}
clonedTradeInItems.add(cloneItem);
}
summaryAccounts = purapAccountingService.generateSummary(clonedTradeInItems);
if (summaryAccounts.size() == 0) {
if (purDoc.shouldGiveErrorForEmptyAccountsProration()) {
GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "trade in");
}
} else {
distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, 2, tradeIn.getAccountingLineClass());
for (PurApAccountingLine distributedAccount : distributedAccounts) {
// KFSPTS-2200 : set item, so it can be verified as discount when validating
if (distributedAccount instanceof PurApAccountingLineBase) {
((PurApAccountingLineBase) distributedAccount).setDiscountTradeIn(true);
}
BigDecimal percent = distributedAccount.getAccountLinePercent();
BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue()));
distributedAccount.setAccountLinePercent(roundedPercent);
// set the accountAmount same as tradeIn amount not line item's amount
resetAccountAmount(distributedAccount, tradeInTotalAmount);
}
tradeIn.setSourceAccountingLines(distributedAccounts);
}
}
}
use of org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase in project cu-kfs by CU-CommunityApps.
the class CuBatchExtractServiceImpl method hasPositiveAndNegative.
/**
* Determines if there are both positive amounts AND negative amounts among the matched PurAp accounting lines.
* This usually could happen in the following cases:
* 1. The account was revised by Financial Officer: positive amount is for charge, negative amount is for refund;
* 2. The account is for Trade-in or Discount item for PREQ, or Less Restocking Fee on Credit Memo.
* 3. Both 1 and 2 happens at same time (for PREQ only; as for CM no revision could happen).
*
* @param matchedPurApAcctLines
* @return
*/
private boolean hasPositiveAndNegative(List<PurApAccountingLineBase> matchedPurApAcctLines) {
boolean hasPostive = false;
boolean hasNegative = false;
for (PurApAccountingLineBase line : matchedPurApAcctLines) {
hasPostive = hasPostive || line.getAmount().isPositive();
hasNegative = hasNegative || line.getAmount().isNegative();
if (hasPostive && hasNegative) {
return true;
}
}
return false;
}
use of org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase in project cu-kfs by CU-CommunityApps.
the class CuBatchExtractServiceImpl method savePOLines.
/**
* @see org.kuali.kfs.module.cam.batch.service.BatchExtractService#savePOLines(List, ExtractProcessLog)
*/
@Transactional
@Override
public HashSet<PurchasingAccountsPayableDocument> savePOLines(List<Entry> poLines, ExtractProcessLog processLog) {
HashSet<PurchasingAccountsPayableDocument> purApDocuments = new HashSet<PurchasingAccountsPayableDocument>();
// This is a list of pending GL entries created after last GL process and Cab Batch extract
// PurAp Account Line history comes from PURAP module
Collection<PurApAccountingLineBase> purapAcctLines = findPurapAccountRevisions();
// Pass the records to reconciliation service method
reconciliationService.reconcile(poLines, purapAcctLines);
// for each valid GL entry there is a collection of valid PO Doc and Account Lines
Collection<GlAccountLineGroup> matchedGroups = reconciliationService.getMatchedGroups();
// Keep track of unique item lines
HashMap<String, PurchasingAccountsPayableItemAsset> assetItems = new HashMap<String, PurchasingAccountsPayableItemAsset>();
// Keep track of unique account lines
HashMap<String, PurchasingAccountsPayableLineAssetAccount> assetAcctLines = new HashMap<String, PurchasingAccountsPayableLineAssetAccount>();
// Keep track of asset lock
HashMap<String, Object> assetLockMap = new HashMap<String, Object>();
// Keep track of purchaseOrderDocument
HashMap<Integer, PurchaseOrderDocument> poDocMap = new HashMap<Integer, PurchaseOrderDocument>();
// KFSMI-7214, add document map for processing multiple items from the same AP doc
HashMap<String, PurchasingAccountsPayableDocument> papdMap = new HashMap<String, PurchasingAccountsPayableDocument>();
for (GlAccountLineGroup group : matchedGroups) {
Entry entry = group.getTargetEntry();
GeneralLedgerEntry generalLedgerEntry = new GeneralLedgerEntry(entry);
GeneralLedgerEntry debitEntry = null;
GeneralLedgerEntry creditEntry = null;
KualiDecimal transactionLedgerEntryAmount = generalLedgerEntry.getTransactionLedgerEntryAmount();
List<PurApAccountingLineBase> matchedPurApAcctLines = group.getMatchedPurApAcctLines();
boolean hasPositiveAndNegative = hasPositiveAndNegative(matchedPurApAcctLines);
boolean nonZero = ObjectUtils.isNotNull(transactionLedgerEntryAmount) && transactionLedgerEntryAmount.isNonZero();
// generally for non-zero transaction ledger amount we should create a single GL entry with that amount,
if (nonZero && !hasPositiveAndNegative) {
businessObjectService.save(generalLedgerEntry);
} else // but if there is FO revision or negative amount lines such as discount, create and save the set of debit(positive) and credit(negative) entries initialized with zero transaction amounts
{
debitEntry = createPositiveGlEntry(entry);
businessObjectService.save(debitEntry);
creditEntry = createNegativeGlEntry(entry);
businessObjectService.save(creditEntry);
}
// KFSMI-7214, create an active document reference map
boolean newApDoc = false;
// KFSMI-7214, find from active document reference map first
PurchasingAccountsPayableDocument cabPurapDoc = papdMap.get(entry.getDocumentNumber());
if (ObjectUtils.isNull(cabPurapDoc)) {
// find from DB
cabPurapDoc = findPurchasingAccountsPayableDocument(entry);
}
// if document is found already, update the active flag
if (ObjectUtils.isNull(cabPurapDoc)) {
cabPurapDoc = createPurchasingAccountsPayableDocument(entry);
newApDoc = true;
}
if (cabPurapDoc != null) {
// KFSMI-7214, add to the cached document map
papdMap.put(entry.getDocumentNumber(), cabPurapDoc);
// we only deal with PREQ or CM, so isPREQ = !isCM, isCM = !PREQ
boolean isPREQ = CamsConstants.PREQ.equals(entry.getFinancialDocumentTypeCode());
boolean hasRevisionWithMixedLines = isPREQ && hasRevisionWithMixedLines(matchedPurApAcctLines);
for (PurApAccountingLineBase purApAccountingLine : matchedPurApAcctLines) {
// KFSMI-7214,tracking down changes on CAB item.
boolean newAssetItem = false;
PurApItem purapItem = purApAccountingLine.getPurapItem();
String itemAssetKey = cabPurapDoc.getDocumentNumber() + "-" + purapItem.getItemIdentifier();
// KFSMI-7214, search CAB item from active object reference map first
PurchasingAccountsPayableItemAsset itemAsset = assetItems.get(itemAssetKey);
if (ObjectUtils.isNull(itemAsset)) {
itemAsset = findMatchingPurapAssetItem(cabPurapDoc, purapItem);
}
// if new item, create and add to the list
if (ObjectUtils.isNull(itemAsset)) {
itemAsset = createPurchasingAccountsPayableItemAsset(cabPurapDoc, purapItem);
cabPurapDoc.getPurchasingAccountsPayableItemAssets().add(itemAsset);
newAssetItem = true;
}
assetItems.put(itemAssetKey, itemAsset);
Long generalLedgerAccountIdentifier = generalLedgerEntry.getGeneralLedgerAccountIdentifier();
KualiDecimal purapAmount = purApAccountingLine.getAmount();
// note that PurAp Doc accounting lines won't have zero amount, so !isPositive = isNegative
boolean isPositive = purapAmount.isPositive();
// trade-in and discount items on PREQ usually have negative amount (unless it's a revision)
boolean usuallyNegative = isItemTypeUsuallyOfNegativeAmount(purapItem.getItemTypeCode());
// decide if current accounting line should be consolidated into debit or credit entry based on the above criteria
boolean isDebitEntry = hasRevisionWithMixedLines ? // case 2.2
(usuallyNegative ? !isPositive : isPositive) : // case 1.1/1.2/2.1
(isPREQ ? isPositive : !isPositive);
GeneralLedgerEntry currentEntry = isDebitEntry ? debitEntry : creditEntry;
if (ObjectUtils.isNull(generalLedgerAccountIdentifier)) {
generalLedgerAccountIdentifier = currentEntry.getGeneralLedgerAccountIdentifier();
}
String acctLineKey = cabPurapDoc.getDocumentNumber() + "-" + itemAsset.getAccountsPayableLineItemIdentifier() + "-" + itemAsset.getCapitalAssetBuilderLineNumber() + "-" + generalLedgerAccountIdentifier;
PurchasingAccountsPayableLineAssetAccount assetAccount = assetAcctLines.get(acctLineKey);
if (ObjectUtils.isNull(assetAccount) && nonZero && !hasPositiveAndNegative) {
// if new unique account line within GL, then create a new account line
assetAccount = createPurchasingAccountsPayableLineAssetAccount(generalLedgerEntry, cabPurapDoc, purApAccountingLine, itemAsset);
assetAcctLines.put(acctLineKey, assetAccount);
itemAsset.getPurchasingAccountsPayableLineAssetAccounts().add(assetAccount);
} else if (!nonZero || hasPositiveAndNegative) {
// if amount is zero, means canceled doc, then create a copy and retain the account line
/*
* KFSMI-9760 / KFSCNTRB-???(FSKD-5097)
* 1. Usually, we consolidate matched accounting lines (for the same account) based on positive/negative amount, i.e.
* 1.1 For PREQ, positive -> debit, negative -> credit;
* That means charges (positive amount) are debit, trade-ins/discounts (negative amount) are credit.
* 1.2. For CM, the opposite, positive -> credit, negative -> debit
* That means payments (positive amount) are credit, Less Restocking Fees (negative amount) are debit.
* 2. However when there is a FO revision on PREQ (CMs don't have revisions), it's more complicated:
* 2.1 If the matched accounting lines are either all for non trade-in/discount items, or all for trade-in/discount items,
* then we still could base the debit/credit on positive/negative amount;
* That means reverse of charges (negative amount) are credit, reverse of trade-ins/discounts (positive amount) are debit.
* 2.2 Otherwise, i.e. the matched accounting lines cover both non trade-in/discount items and trade-in/discount items,
* In this case we prefer to consolidate based on revision,
* that means the original charges and trade-in/discounts are combined together,
* while the reversed charges and trade-in/discounts are combined together;
* So: original charge + original trade-in/discount -> debit, reversed charge + reversed trade-in/discount -> credit
* 3. On top of these, we ensure that the final capital asset GL entries created is a debit if the consolidated amount is positive, and vice versa.
* Note: In general, the consolidated amount for debit entry should already be positive, and vice versa. But there could be special cases,
* for ex, in the case of 2.2, if the revision is only on discount, then the credit entry for the reverse would come out as positive, so we need
* to swap it into a debit entry. This means, we will have 2 debit entries, one for the original lines, the other for the reversed discount line.
*/
// during calculation, regard D/C code as a +/- sign in front of the amount
KualiDecimal oldAmount = currentEntry.getTransactionLedgerEntryAmount();
oldAmount = isDebitEntry ? oldAmount : oldAmount.negated();
KualiDecimal newAmount = oldAmount.add(purapAmount);
newAmount = isDebitEntry ? newAmount : newAmount.negated();
currentEntry.setTransactionLedgerEntryAmount(newAmount);
if (ObjectUtils.isNotNull(assetAccount)) {
// if account line key matches within same GL Entry, combine the amount
assetAccount.setItemAccountTotalAmount(assetAccount.getItemAccountTotalAmount().add(purApAccountingLine.getAmount()));
} else {
assetAccount = createPurchasingAccountsPayableLineAssetAccount(currentEntry, cabPurapDoc, purApAccountingLine, itemAsset);
assetAcctLines.put(acctLineKey, assetAccount);
itemAsset.getPurchasingAccountsPayableLineAssetAccounts().add(assetAccount);
}
} else if (ObjectUtils.isNotNull(assetAccount)) {
// if account line key matches within same GL Entry, combine the amount
assetAccount.setItemAccountTotalAmount(assetAccount.getItemAccountTotalAmount().add(purApAccountingLine.getAmount()));
}
// KFSMI-7214: fixed OJB auto-update object issue.
if (!newAssetItem) {
businessObjectService.save(itemAsset);
}
businessObjectService.save(cabPurapDoc);
// Add to the asset lock table if purap has asset number information
addAssetLocks(assetLockMap, cabPurapDoc, purapItem, itemAsset.getAccountsPayableLineItemIdentifier(), poDocMap);
}
// since the real amount being positive/negative shall be solely indicated by the D/C code.
if (debitEntry != null) {
KualiDecimal amount = debitEntry.getTransactionLedgerEntryAmount();
if (amount.isNegative()) {
debitEntry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
debitEntry.setTransactionLedgerEntryAmount(amount.negated());
}
businessObjectService.save(debitEntry);
}
if (creditEntry != null) {
KualiDecimal amount = creditEntry.getTransactionLedgerEntryAmount();
if (amount.isNegative()) {
creditEntry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
creditEntry.setTransactionLedgerEntryAmount(amount.negated());
}
businessObjectService.save(creditEntry);
}
// batch.
if (newApDoc) {
purApDocuments.add(cabPurapDoc);
}
} else {
LOG.error("Could not create a valid PurchasingAccountsPayableDocument object for document number " + entry.getDocumentNumber());
}
}
updateProcessLog(processLog, reconciliationService);
return purApDocuments;
}
use of org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase in project cu-kfs by CU-CommunityApps.
the class CuUserFavoriteAccountServiceImplTest method validateAccount.
private void validateAccount(String accountType, Class accountClass) {
final FavoriteAccount favoriteAccount = FavoriteAccountFixture.FAVORITE_ACCOUNT_1.createFavoriteAccount();
final GeneralLedgerPendingEntrySourceDetail acct = userFavoriteAccountService.getPopulatedNewAccount(favoriteAccount, accountClass);
Assert.assertNotNull("Account should be populated", acct);
Assert.assertTrue("Account should be " + accountType + " Account", accountClass.isInstance(acct));
Assert.assertEquals("Account Number should be populated", favoriteAccount.getAccountNumber(), acct.getAccountNumber());
Assert.assertEquals("Object Code should be populated", favoriteAccount.getFinancialObjectCode(), acct.getFinancialObjectCode());
if (acct instanceof IWantAccount) {
final KualiDecimal accountLinePercent = ((IWantAccount) acct).getAmountOrPercent();
Assert.assertEquals("Incorrect percentage (comparison against 100% should have been zero)", 0, accountLinePercent.compareTo(new KualiDecimal(100)));
Assert.assertEquals("Amount-or-Percent indicator should be Percent", CUPurapConstants.PERCENT, ((IWantAccount) acct).getUseAmountOrPercent());
} else {
final BigDecimal accountLinePercent = ((PurApAccountingLineBase) acct).getAccountLinePercent();
Assert.assertEquals("Incorrect percentage (comparison against 100% should have been zero)", 0, accountLinePercent.compareTo(new BigDecimal(100)));
}
}
use of org.kuali.kfs.module.purap.businessobject.PurApAccountingLineBase in project cu-kfs by CU-CommunityApps.
the class CuBatchExtractServiceImpl method hasRevisionWithMixedLines.
/**
* Determines if the matched PurAp accounting lines have revisions and the account is used in both line items and trade-in/discount items.
* If so, the trade-in/discount accounting lines need to be consolidated differently than simply by positive/negative amount.
* Note: This method only applies to PREQ, since no revision could happen to CM.
*
* @param matchedPurApAcctLines List of matched PurAp accounting lines to check for multiple discount items
* @return true if multiple discount items, false otherwise
*/
private boolean hasRevisionWithMixedLines(List<PurApAccountingLineBase> matchedPurApAcctLines) {
boolean hasItemsUsuallyNegative = false;
boolean hasOthers = false;
boolean hasRevision = false;
HashSet<Integer> itemIdentifiers = new HashSet<Integer>();
for (PurApAccountingLineBase purApAccountingLine : matchedPurApAcctLines) {
PurApItem purapItem = purApAccountingLine.getPurapItem();
if (isItemTypeUsuallyOfNegativeAmount(purapItem.getItemTypeCode())) {
hasItemsUsuallyNegative = true;
} else {
hasOthers = true;
}
// when we hit the same item twice within the matched lines, which share the same account, then we find a revision
if (itemIdentifiers.contains(purApAccountingLine.getItemIdentifier())) {
hasRevision = true;
} else {
itemIdentifiers.add(purApAccountingLine.getItemIdentifier());
}
if (hasRevision && hasItemsUsuallyNegative && hasOthers) {
return true;
}
}
return false;
}
Aggregations