001/* 002 * Copyright 2007 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 */ 016package org.kuali.ole.select.document.service.impl; 017 018import org.kuali.ole.module.purap.PurapConstants; 019import org.kuali.ole.module.purap.businessobject.PurApAccountingLine; 020import org.kuali.ole.module.purap.businessobject.PurApItem; 021import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument; 022import org.kuali.ole.module.purap.document.service.PurapService; 023import org.kuali.ole.module.purap.service.impl.PurapAccountingServiceImpl; 024import org.kuali.ole.module.purap.util.PurApItemUtils; 025import org.kuali.ole.module.purap.util.PurApObjectUtils; 026import org.kuali.ole.select.businessobject.OlePaymentRequestItem; 027import org.kuali.ole.select.businessobject.OlePurchaseOrderItem; 028import org.kuali.ole.select.document.service.OlePurapAccountingService; 029import org.kuali.ole.sys.OLEConstants; 030import org.kuali.ole.sys.businessobject.AccountingLineBase; 031import org.kuali.ole.sys.businessobject.SourceAccountingLine; 032import org.kuali.ole.sys.service.NonTransactional; 033import org.kuali.rice.core.api.util.type.KualiDecimal; 034import org.kuali.rice.krad.util.ObjectUtils; 035import org.kuali.ole.select.businessobject.OleInvoiceItem; 036 037import java.math.BigDecimal; 038import java.math.RoundingMode; 039import java.util.*; 040 041/** 042 * Contains a number of helper methods to deal with accounts on Purchasing Accounts Payable Documents 043 */ 044 045@NonTransactional 046public class OlePurapAccountingServiceImpl extends PurapAccountingServiceImpl implements OlePurapAccountingService { 047 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OlePurapAccountingServiceImpl.class); 048 049 private PurapService purapService; 050 051 052 @Override 053 public void setPurapService(PurapService purapService) { 054 this.purapService = purapService; 055 } 056 057 /** 058 * calculates values for a list of accounting lines based on an amount 059 * 060 * @param sourceAccountingLines 061 * @param totalAmount 062 */ 063 @Override 064 public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) { 065 if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) { 066 067 KualiDecimal accountTotal = KualiDecimal.ZERO; 068 T lastAccount = null; 069 BigDecimal accountTotalPercent = BigDecimal.ZERO; 070 071 072 for (T account : sourceAccountingLines) { 073 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) { 074 BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100)); 075 account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR))); 076 } else if (ObjectUtils.isNotNull(account.getAmount()) && ObjectUtils.isNull(account.getAccountLinePercent())) { 077 KualiDecimal dollar = account.getAmount().multiply(new KualiDecimal(100)); 078 BigDecimal dollarToPercent = dollar.bigDecimalValue().divide((totalAmount.bigDecimalValue()), 0, RoundingMode.FLOOR); 079 account.setAccountLinePercent(dollarToPercent); 080 } else if (ObjectUtils.isNotNull(account.getAmount()) && ObjectUtils.isNotNull(account.getAccountLinePercent())) { 081 account.setAmount(account.getAmount()); 082 account.setAccountLinePercent(account.getAccountLinePercent()); 083 } else { 084 account.setAmount(KualiDecimal.ZERO); 085 } 086 accountTotal = accountTotal.add(account.getAmount()); 087 lastAccount = account; 088 } 089 090 if (lastAccount != null) { 091 KualiDecimal difference = new KualiDecimal(0); 092 difference = totalAmount.subtract(accountTotal); 093 if (ObjectUtils.isNotNull(lastAccount.getAmount())) { 094 if ((difference.abs()).isLessEqual(new KualiDecimal(1).multiply(new KualiDecimal(sourceAccountingLines.size()).divide(new KualiDecimal(2))))) { 095 096 lastAccount.setAmount(lastAccount.getAmount().add(difference)); 097 } else { 098 lastAccount.setAmount(lastAccount.getAmount()); 099 } 100 } 101 BigDecimal percentDifference = new BigDecimal(100).subtract(accountTotalPercent).setScale(BIG_DECIMAL_SCALE,BigDecimal.ROUND_CEILING); 102 if (ObjectUtils.isNotNull(lastAccount.getAccountLinePercent())) { 103 104 KualiDecimal differencePercent = (((new KualiDecimal(accountTotalPercent)).subtract(new KualiDecimal(100))).abs()); 105 if ((differencePercent.abs()).isLessEqual(new KualiDecimal(1).multiply((new KualiDecimal(sourceAccountingLines.size()).divide(new KualiDecimal(2)))))) { 106 lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent().add(percentDifference)); 107 } else { 108 lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent()); 109 } 110 } 111 } 112 } else { 113 // zero out if extended price is zero 114 for (T account : sourceAccountingLines) { 115 account.setAmount(KualiDecimal.ZERO); 116 } 117 } 118 } 119 120 /** 121 * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateAccountDistributionForProration(java.util.List, 122 * org.kuali.rice.kns.util.KualiDecimal, java.lang.Integer) 123 */ 124 @Override 125 public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale, Class clazz) { 126 /* String methodName = "generateAccountDistributionForProration()"; 127 if (LOG.isDebugEnabled()) { 128 LOG.debug(methodName + " started"); 129 } 130 List<PurApAccountingLine> newAccounts = new ArrayList(); 131 132 if (totalAmount.isZero()) { 133 throwRuntimeException(methodName, "Purchasing/Accounts Payable account distribution for proration does not allow zero dollar total."); 134 } 135 136 BigDecimal percentTotal = BigDecimal.ZERO; 137 BigDecimal totalAmountBigDecimal = totalAmount.bigDecimalValue(); 138 for (SourceAccountingLine accountingLine : accounts) { 139 if (LOG.isDebugEnabled()) { 140 LOG.debug(methodName + " " + accountingLine.getAccountNumber() + " " + accountingLine.getAmount() + "/" + totalAmountBigDecimal); 141 } 142 // BigDecimal pct = accountingLine.getAmount().bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE); 143 BigDecimal pct = BigDecimal.ZERO; 144 if (accountingLine.getSourceAcountLinePercent() != null) 145 pct = accountingLine.getSourceAcountLinePercent(); 146 else 147 pct = accountingLine.getAmount().bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE); 148 pct = pct.stripTrailingZeros().multiply(ONE_HUNDRED); 149 150 if (LOG.isDebugEnabled()) { 151 LOG.debug(methodName + " pct = " + pct + " (trailing zeros removed)"); 152 } 153 154 BigDecimal lowestPossible = this.getLowestPossibleRoundUpNumber(); 155 if (lowestPossible.compareTo(pct) <= 0) { 156 PurApAccountingLine newAccountingLine; 157 newAccountingLine = null; 158 159 try { 160 newAccountingLine = (PurApAccountingLine) clazz.newInstance(); 161 } catch (InstantiationException e) { 162 e.printStackTrace(); 163 } catch (IllegalAccessException e) { 164 e.printStackTrace(); 165 } 166 167 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine); 168 newAccountingLine.setAccountLinePercent(pct); 169 if (LOG.isDebugEnabled()) { 170 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent()); 171 } 172 newAccounts.add(newAccountingLine); 173 percentTotal = percentTotal.add(newAccountingLine.getAccountLinePercent()); 174 if (LOG.isDebugEnabled()) { 175 LOG.debug(methodName + " total = " + percentTotal); 176 } 177 } 178 } 179 180 if ((percentTotal.compareTo(BigDecimal.ZERO)) == 0) { 181 *//* 182 * This means there are so many accounts or so strange a distribution that we can't round properly... not sure of viable 183 * solution 184 *//* 185 throwRuntimeException(methodName, "Can't round properly due to number of accounts"); 186 } 187 188 // Now deal with rounding 189 if ((ONE_HUNDRED.compareTo(percentTotal)) < 0) { 190 *//* 191 * The total percent is greater than one hundred Here we find the account that occurs latest in our list with a percent 192 * that is higher than the difference and we subtract off the difference 193 *//* 194 BigDecimal difference = percentTotal.subtract(ONE_HUNDRED); 195 if (LOG.isDebugEnabled()) { 196 LOG.debug(methodName + " Rounding up by " + difference); 197 } 198 199 boolean foundAccountToUse = false; 200 int currentNbr = newAccounts.size() - 1; 201 while (currentNbr >= 0) { 202 PurApAccountingLine potentialSlushAccount = (PurApAccountingLine) newAccounts.get(currentNbr); 203 204 if ((difference.compareTo(potentialSlushAccount.getAccountLinePercent())) < 0) { 205 // the difference amount is less than the current accounts percent... use this account 206 // the 'potentialSlushAccount' technically is now the true 'Slush Account' 207 potentialSlushAccount.setAccountLinePercent(potentialSlushAccount.getAccountLinePercent().subtract(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2)); 208 foundAccountToUse = true; 209 break; 210 } 211 currentNbr--; 212 } 213 214 if (!foundAccountToUse) { 215 *//* 216 * We could not find any account in our list where the percent of that account was greater than that of the 217 * difference... doing so on just any account could result in a negative percent value 218 *//* 219 throwRuntimeException(methodName, "Can't round properly due to math calculation error"); 220 } 221 222 } else if ((ONE_HUNDRED.compareTo(percentTotal)) > 0) { 223 *//* 224 * The total percent is less than one hundred Here we find the last account in our list and add the remaining required 225 * percent to its already calculated percent 226 *//* 227 BigDecimal difference = ONE_HUNDRED.subtract(percentTotal); 228 if (LOG.isDebugEnabled()) { 229 LOG.debug(methodName + " Rounding down by " + difference); 230 } 231 PurApAccountingLine slushAccount = (PurApAccountingLine) newAccounts.get(newAccounts.size() - 1); 232 slushAccount.setAccountLinePercent(slushAccount.getAccountLinePercent().add(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2)); 233 } 234 if (LOG.isDebugEnabled()) { 235 LOG.debug(methodName + " ended"); 236 } 237 return newAccounts;*/ 238 return super.generateAccountDistributionForProration(accounts, totalAmount, percentScale, clazz); 239 } 240 241 /** 242 * Generates an account summary, that is it creates a list of source accounts by rounding up the purap accounts off of the purap 243 * items. 244 * 245 * @param items the items to determ 246 * @param itemTypeCodes the item types to determine whether to look at an item in combination with itemTypeCodesAreIncluded 247 * @param itemTypeCodesAreIncluded value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables 248 * @param useZeroTotals whether to include items with a zero dollar total 249 * @param useAlternateAmount an alternate amount used in certain cases for GL entry 250 * @return a list of source accounts 251 */ 252 @Override 253 protected List<SourceAccountingLine> generateAccountSummary(List<PurApItem> items, Set<String> itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals, Boolean useAlternateAmount, Boolean useTaxIncluded, Boolean taxableOnly) { 254 255 items = PurApItemUtils.getAboveTheLineOnly(items); 256 List<PurApItem> itemsToProcess = getProcessablePurapItems(items, itemTypeCodes, itemTypeCodesAreIncluded, useZeroTotals); 257 Map<PurApAccountingLine, KualiDecimal> accountMap = new HashMap<PurApAccountingLine, KualiDecimal>(); 258 Map<PurApAccountingLine, KualiDecimal> qtyMap = new HashMap<PurApAccountingLine, KualiDecimal>(); 259 260 for (PurApItem currentItem : itemsToProcess) { 261 if (PurApItemUtils.checkItemActive(currentItem)) { 262 List<PurApAccountingLine> sourceAccountingLines = currentItem.getSourceAccountingLines(); 263 264 // skip if item is not taxable and taxable only flag has been set 265 if (taxableOnly) { 266 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument(); 267 if (!purapService.isTaxableForSummary(document.isUseTaxIndicator(), purapService.getDeliveryState(document), currentItem)) { 268 continue; 269 } 270 } 271 272 if (!useTaxIncluded) { 273 // if no use tax set the source accounting lines to a clone so we can update 274 // them to be based on the non tax amount 275 PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(currentItem); 276 sourceAccountingLines = cloneItem.getSourceAccountingLines(); 277 updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount()); 278 } 279 280 for (PurApAccountingLine account : sourceAccountingLines) { 281 282 // skip account if not taxable and taxable only flag is set 283 if (taxableOnly) { 284 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument(); 285 // check if account is not taxable, if not skip this account 286 if (!purapService.isAccountingLineTaxable(account, purapService.isDeliveryStateTaxable(purapService.getDeliveryState(document)))) { 287 continue; 288 } 289 } 290 291 // getting the total to set on the account 292 KualiDecimal total = KualiDecimal.ZERO; 293 KualiDecimal qty = KualiDecimal.ZERO; 294 if (accountMap.containsKey(account)) { 295 total = accountMap.get(account); 296 } 297 if (qtyMap.containsKey(account)) { 298 qty = qtyMap.get(account); 299 } 300 301 if (useAlternateAmount) { 302 total = total.add(account.getAlternateAmountForGLEntryCreation()); 303 } else { 304 if (ObjectUtils.isNotNull(account.getAmount())) { 305 total = total.add(account.getAmount()); 306 } 307 if (currentItem.getItemQuantity() != null && account.getAccountLinePercent() != null) { 308 qty = qty.add(currentItem.getItemQuantity().divide(new KualiDecimal(ONE_HUNDRED)).multiply(new KualiDecimal(account.getAccountLinePercent()))); 309 } 310 } 311 312 accountMap.put(account, total); 313 qtyMap.put(account, qty); 314 } 315 } 316 } 317 318 // convert list of PurApAccountingLine objects to SourceAccountingLineObjects 319 Iterator<PurApAccountingLine> iterator = accountMap.keySet().iterator(); 320 List<SourceAccountingLine> sourceAccounts = new ArrayList<SourceAccountingLine>(); 321 for (Iterator<PurApAccountingLine> iter = iterator; iter.hasNext(); ) { 322 PurApAccountingLine accountToConvert = iter.next(); 323 if (accountToConvert.isEmpty()) { 324 String errorMessage = "Found an 'empty' account in summary generation " + accountToConvert.toString(); 325 LOG.error("generateAccountSummary() " + errorMessage); 326 throw new RuntimeException(errorMessage); 327 } 328 KualiDecimal sourceLineTotal = accountMap.get(accountToConvert); 329 SourceAccountingLine sourceLine = accountToConvert.generateSourceAccountingLine(); 330 sourceLine.setAmount(sourceLineTotal); 331 sourceLine.setSourceAccountQty(qtyMap.get(accountToConvert)); 332 sourceAccounts.add(sourceLine); 333 } 334 335 // sort the sourceAccounts list first by account number, then by object code, ignoring chart code 336 Collections.sort(sourceAccounts, new Comparator<SourceAccountingLine>() { 337 @Override 338 public int compare(SourceAccountingLine sal1, SourceAccountingLine sal2) { 339 int compare = 0; 340 if (sal1 != null && sal2 != null) { 341 if (sal1.getAccountNumber() != null && sal2.getAccountNumber() != null) { 342 compare = sal1.getAccountNumber().compareTo(sal2.getAccountNumber()); 343 if (compare == 0) { 344 if (sal1.getFinancialObjectCode() != null && sal2.getFinancialObjectCode() != null) { 345 compare = sal1.getFinancialObjectCode().compareTo(sal2.getFinancialObjectCode()); 346 } 347 } 348 } 349 } 350 return compare; 351 } 352 }); 353 354 return sourceAccounts; 355 } 356 357 /** 358 * Generates an account summary, that is it creates a list of source accounts by rounding up the purap accounts off of the purap 359 * items. 360 * 361 * @param items the items to determ 362 * @param itemTypeCodes the item types to determine whether to look at an item in combination with itemTypeCodesAreIncluded 363 * @param itemTypeCodesAreIncluded value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables 364 * @param useZeroTotals whether to include items with a zero dollar total 365 * @param useAlternateAmount an alternate amount used in certain cases for GL entry 366 * @return a list of source accounts 367 */ 368 protected List<SourceAccountingLine> generateAccountSummaryForManual(List<PurApItem> items, Set<String> itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals, Boolean useAlternateAmount, Boolean useTaxIncluded, Boolean taxableOnly) { 369 items = PurApItemUtils.getAboveTheLineOnly(items); 370 List<PurApItem> itemsToProcess = getProcessablePurapItems(items, itemTypeCodes, itemTypeCodesAreIncluded, useZeroTotals); 371 Map<PurApAccountingLine, KualiDecimal> accountMap = new HashMap<PurApAccountingLine, KualiDecimal>(); 372 373 for (PurApItem currentItem : itemsToProcess) { 374 if (PurApItemUtils.checkItemActive(currentItem)) { 375 List<PurApAccountingLine> sourceAccountingLines = currentItem.getSourceAccountingLines(); 376 377 // skip if item is not taxable and taxable only flag has been set 378 if (taxableOnly) { 379 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument(); 380 if (!purapService.isTaxableForSummary(document.isUseTaxIndicator(), purapService.getDeliveryState(document), currentItem)) { 381 continue; 382 } 383 } 384 385 if (!useTaxIncluded) { 386 // if no use tax set the source accounting lines to a clone so we can update 387 // them to be based on the non tax amount 388 PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(currentItem); 389 sourceAccountingLines = cloneItem.getSourceAccountingLines(); 390 updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount()); 391 } 392 393 for (PurApAccountingLine account : sourceAccountingLines) { 394 395 // skip account if not taxable and taxable only flag is set 396 if (taxableOnly) { 397 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument(); 398 // check if account is not taxable, if not skip this account 399 if (!purapService.isAccountingLineTaxable(account, purapService.isDeliveryStateTaxable(purapService.getDeliveryState(document)))) { 400 continue; 401 } 402 } 403 404 // getting the total to set on the account 405 KualiDecimal total = KualiDecimal.ZERO; 406 if (accountMap.containsKey(account)) { 407 total = accountMap.get(account); 408 } 409 410 if (useAlternateAmount) { 411 total = total.add(account.getAlternateAmountForGLEntryCreation()); 412 } else { 413 if (ObjectUtils.isNotNull(account.getAmount())) { 414 total = total.add(account.getAmount()); 415 } 416 } 417 418 accountMap.put(account, total); 419 } 420 } 421 } 422 423 // convert list of PurApAccountingLine objects to SourceAccountingLineObjects 424 Iterator<PurApAccountingLine> iterator = accountMap.keySet().iterator(); 425 List<SourceAccountingLine> sourceAccounts = new ArrayList<SourceAccountingLine>(); 426 for (Iterator<PurApAccountingLine> iter = iterator; iter.hasNext(); ) { 427 PurApAccountingLine accountToConvert = iter.next(); 428 if (accountToConvert.isEmpty()) { 429 String errorMessage = "Found an 'empty' account in summary generation " + accountToConvert.toString(); 430 LOG.error("generateAccountSummary() " + errorMessage); 431 throw new RuntimeException(errorMessage); 432 } 433 SourceAccountingLine sourceLine = accountToConvert.generateSourceAccountingLine(); 434 sourceAccounts.add(sourceLine); 435 } 436 437 // sort the sourceAccounts list first by account number, then by object code, ignoring chart code 438 Collections.sort(sourceAccounts, new Comparator<SourceAccountingLine>() { 439 @Override 440 public int compare(SourceAccountingLine sal1, SourceAccountingLine sal2) { 441 int compare = 0; 442 if (sal1 != null && sal2 != null) { 443 if (sal1.getAccountNumber() != null && sal2.getAccountNumber() != null) { 444 compare = sal1.getAccountNumber().compareTo(sal2.getAccountNumber()); 445 if (compare == 0) { 446 if (sal1.getFinancialObjectCode() != null && sal2.getFinancialObjectCode() != null) { 447 compare = sal1.getFinancialObjectCode().compareTo(sal2.getFinancialObjectCode()); 448 } 449 } 450 } 451 } 452 return compare; 453 } 454 }); 455 456 return sourceAccounts; 457 } 458 459 @Override 460 public List<PurApAccountingLine> generateAccountDistributionForProrationByQty(List<SourceAccountingLine> accounts, KualiDecimal totatQty, Integer percentScale, Class clazz) { 461 String methodName = "generateAccountDistributionForProrationByQty()"; 462 if (LOG.isDebugEnabled()) { 463 LOG.debug(methodName + " started"); 464 } 465 List<PurApAccountingLine> newAccounts = new ArrayList(); 466 467 if (totatQty.isZero()) { 468 throwRuntimeException(methodName, "Purchasing/Accounts Payable account distribution for proration does not allow zero dollar total."); 469 } 470 471 BigDecimal percentTotal = BigDecimal.ZERO; 472 BigDecimal totalQtyBigDecimal = totatQty.bigDecimalValue(); 473 for (SourceAccountingLine accountingLine : accounts) { 474 KualiDecimal qty = KualiDecimal.ZERO; 475 if (ObjectUtils.isNotNull(accountingLine.getSourceAccountQty())) { 476 qty = accountingLine.getSourceAccountQty(); 477 } 478 479 if (LOG.isDebugEnabled()) { 480 LOG.debug(methodName + " " + accountingLine.getAccountNumber() + " " + qty + "/" + totalQtyBigDecimal); 481 } 482 BigDecimal pct = qty.bigDecimalValue().divide(totalQtyBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE); 483 pct = pct.stripTrailingZeros().multiply(ONE_HUNDRED); 484 485 if (LOG.isDebugEnabled()) { 486 LOG.debug(methodName + " pct = " + pct + " (trailing zeros removed)"); 487 } 488 489 BigDecimal lowestPossible = this.getLowestPossibleRoundUpNumber(); 490 if (lowestPossible.compareTo(pct) <= 0) { 491 PurApAccountingLine newAccountingLine; 492 newAccountingLine = null; 493 494 try { 495 newAccountingLine = (PurApAccountingLine) clazz.newInstance(); 496 } catch (InstantiationException e) { 497 e.printStackTrace(); 498 } catch (IllegalAccessException e) { 499 e.printStackTrace(); 500 } 501 502 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine); 503 newAccountingLine.setAccountLinePercent(pct); 504 if (LOG.isDebugEnabled()) { 505 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent()); 506 } 507 newAccounts.add(newAccountingLine); 508 percentTotal = percentTotal.add(newAccountingLine.getAccountLinePercent()); 509 if (LOG.isDebugEnabled()) { 510 LOG.debug(methodName + " total = " + percentTotal); 511 } 512 } 513 } 514 515 if ((percentTotal.compareTo(BigDecimal.ZERO)) == 0) { 516 /* 517 * This means there are so many accounts or so strange a distribution that we can't round properly... not sure of viable 518 * solution 519 */ 520 throwRuntimeException(methodName, "Can't round properly due to number of accounts"); 521 } 522 523 // Now deal with rounding 524 if ((ONE_HUNDRED.compareTo(percentTotal)) < 0) { 525 /* 526 * The total percent is greater than one hundred Here we find the account that occurs latest in our list with a percent 527 * that is higher than the difference and we subtract off the difference 528 */ 529 BigDecimal difference = percentTotal.subtract(ONE_HUNDRED); 530 if (LOG.isDebugEnabled()) { 531 LOG.debug(methodName + " Rounding up by " + difference); 532 } 533 534 boolean foundAccountToUse = false; 535 int currentNbr = newAccounts.size() - 1; 536 while (currentNbr >= 0) { 537 PurApAccountingLine potentialSlushAccount = newAccounts.get(currentNbr); 538 539 BigDecimal linePercent = BigDecimal.ZERO; 540 if (ObjectUtils.isNotNull(potentialSlushAccount.getAccountLinePercent())) { 541 linePercent = potentialSlushAccount.getAccountLinePercent(); 542 } 543 544 if ((difference.compareTo(linePercent)) < 0) { 545 // the difference amount is less than the current accounts percent... use this account 546 // the 'potentialSlushAccount' technically is now the true 'Slush Account' 547 potentialSlushAccount.setAccountLinePercent(linePercent.subtract(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2)); 548 foundAccountToUse = true; 549 break; 550 } 551 currentNbr--; 552 } 553 554 if (!foundAccountToUse) { 555 /* 556 * We could not find any account in our list where the percent of that account was greater than that of the 557 * difference... doing so on just any account could result in a negative percent value 558 */ 559 throwRuntimeException(methodName, "Can't round properly due to math calculation error"); 560 } 561 562 } else if ((ONE_HUNDRED.compareTo(percentTotal)) > 0) { 563 /* 564 * The total percent is less than one hundred Here we find the last account in our list and add the remaining required 565 * percent to its already calculated percent 566 */ 567 BigDecimal difference = ONE_HUNDRED.subtract(percentTotal); 568 if (LOG.isDebugEnabled()) { 569 LOG.debug(methodName + " Rounding down by " + difference); 570 } 571 PurApAccountingLine slushAccount = newAccounts.get(newAccounts.size() - 1); 572 573 BigDecimal slushLinePercent = BigDecimal.ZERO; 574 if (ObjectUtils.isNotNull(slushAccount.getAccountLinePercent())) { 575 slushLinePercent = slushAccount.getAccountLinePercent(); 576 } 577 578 slushAccount.setAccountLinePercent(slushLinePercent.add(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2)); 579 } 580 if (LOG.isDebugEnabled()) { 581 LOG.debug(methodName + " ended"); 582 } 583 return newAccounts; 584 } 585 586 @Override 587 public List<SourceAccountingLine> generateSummaryForManual(List<PurApItem> items) { 588 String methodName = "generateSummary()"; 589 if (LOG.isDebugEnabled()) { 590 LOG.debug(methodName + " started"); 591 } 592 List<SourceAccountingLine> returnList = generateAccountSummaryForManual(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 593 if (LOG.isDebugEnabled()) { 594 LOG.debug(methodName + " ended"); 595 } 596 return returnList; 597 } 598 599 @Override 600 public List<PurApAccountingLine> generateAccountDistributionForProrationByManual(List<SourceAccountingLine> accounts, Class clazz) { 601 String methodName = "generateAccountDistributionForProrationByManual()"; 602 if (LOG.isDebugEnabled()) { 603 LOG.debug(methodName + " started"); 604 } 605 List<PurApAccountingLine> newAccounts = new ArrayList(); 606 BigDecimal percent = new BigDecimal(0); 607 if (accounts.size() > 0) { 608 percent = new BigDecimal(100).divide(new BigDecimal(accounts.size()), 2, RoundingMode.HALF_UP); 609 } 610 for (SourceAccountingLine accountingLine : accounts) { 611 612 if (LOG.isDebugEnabled()) { 613 LOG.debug(methodName + " " + accountingLine.getAccountNumber()); 614 } 615 PurApAccountingLine newAccountingLine; 616 newAccountingLine = null; 617 618 try { 619 newAccountingLine = (PurApAccountingLine) clazz.newInstance(); 620 } catch (InstantiationException e) { 621 e.printStackTrace(); 622 } catch (IllegalAccessException e) { 623 e.printStackTrace(); 624 } 625 626 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine); 627 newAccountingLine.setAccountLinePercent(percent); 628 newAccountingLine.setAmount(new KualiDecimal(0)); 629 if (LOG.isDebugEnabled()) { 630 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent()); 631 } 632 newAccounts.add(newAccountingLine); 633 } 634 if (LOG.isDebugEnabled()) { 635 LOG.debug(methodName + " ended"); 636 } 637 return newAccounts; 638 } 639 640 641}