001 /* 002 * Copyright 2011 The Kuali Foundation. 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.kfs.module.purap.service.impl; 017 018 import java.math.BigDecimal; 019 import java.util.ArrayList; 020 import java.util.Collections; 021 import java.util.Comparator; 022 import java.util.HashMap; 023 import java.util.Iterator; 024 import java.util.List; 025 import java.util.Map; 026 import java.util.Set; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.kuali.kfs.module.purap.PurapConstants; 030 import org.kuali.kfs.module.purap.PurapConstants.PurapDocTypeCodes; 031 import org.kuali.kfs.module.purap.PurapKeyConstants; 032 import org.kuali.kfs.module.purap.PurapParameterConstants.NRATaxParameters; 033 import org.kuali.kfs.module.purap.businessobject.PaymentRequestAccount; 034 import org.kuali.kfs.module.purap.businessobject.PaymentRequestItem; 035 import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; 036 import org.kuali.kfs.module.purap.businessobject.PurApItem; 037 import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax; 038 import org.kuali.kfs.module.purap.businessobject.PurApSummaryItem; 039 import org.kuali.kfs.module.purap.dataaccess.PurApAccountingDao; 040 import org.kuali.kfs.module.purap.document.PaymentRequestDocument; 041 import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument; 042 import org.kuali.kfs.module.purap.document.service.PurapService; 043 import org.kuali.kfs.module.purap.service.PurapAccountingService; 044 import org.kuali.kfs.module.purap.util.PurApItemUtils; 045 import org.kuali.kfs.module.purap.util.PurApObjectUtils; 046 import org.kuali.kfs.module.purap.util.SummaryAccount; 047 import org.kuali.kfs.module.purap.util.UseTaxContainer; 048 import org.kuali.kfs.sys.businessobject.AccountingLineBase; 049 import org.kuali.kfs.sys.businessobject.SourceAccountingLine; 050 import org.kuali.kfs.sys.service.NonTransactional; 051 import org.kuali.rice.kns.service.ParameterService; 052 import org.kuali.rice.kns.util.GlobalVariables; 053 import org.kuali.rice.kns.util.KualiDecimal; 054 import org.kuali.rice.kns.util.ObjectUtils; 055 /** 056 * 057 * Contains a number of helper methods to deal with accounts on Purchasing Accounts Payable Documents 058 */ 059 060 @NonTransactional 061 public class PurapAccountingServiceImpl implements PurapAccountingService { 062 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapAccountingServiceImpl.class); 063 064 protected static final BigDecimal ONE_HUNDRED = new BigDecimal(100); 065 protected static final int SCALE = 340; 066 protected static final int BIG_DECIMAL_ROUNDING_MODE = BigDecimal.ROUND_HALF_UP; 067 068 // local constants 069 protected static final Boolean ITEM_TYPES_INCLUDED_VALUE = Boolean.TRUE;; 070 protected static final Boolean ITEM_TYPES_EXCLUDED_VALUE = Boolean.FALSE; 071 protected static final Boolean ZERO_TOTALS_RETURNED_VALUE = Boolean.TRUE; 072 protected static final Boolean ZERO_TOTALS_NOT_RETURNED_VALUE = Boolean.FALSE; 073 protected static final Boolean ALTERNATE_AMOUNT_USED = Boolean.TRUE; 074 protected static final Boolean ALTERNATE_AMOUNT_NOT_USED = Boolean.FALSE; 075 protected static final Boolean USE_TAX_INCLUDED = Boolean.TRUE; 076 protected static final Boolean USE_TAX_EXCLUDED = Boolean.FALSE; 077 078 private ParameterService parameterService; 079 private PurApAccountingDao purApAccountingDao; 080 private PurapService purapService; 081 082 /** 083 * 084 * gets the lowest possible number for rounding, it works for ROUND_HALF_UP 085 * @return a BigDecimal representing the lowest possible number for rounding 086 */ 087 protected BigDecimal getLowestPossibleRoundUpNumber() { 088 BigDecimal startingDigit = new BigDecimal(0.5); 089 if (SCALE != 0) { 090 startingDigit = startingDigit.movePointLeft(SCALE); 091 } 092 return startingDigit; 093 } 094 095 /** 096 * 097 * Helper method to log and throw an error 098 * @param methodName the method it's coming from 099 * @param errorMessage the actual error 100 */ 101 protected void throwRuntimeException(String methodName, String errorMessage) { 102 LOG.error(methodName + " " + errorMessage); 103 throw new RuntimeException(errorMessage); 104 } 105 106 /** 107 * @deprecated 108 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateAccountDistributionForProration(java.util.List, 109 * org.kuali.rice.kns.util.KualiDecimal, java.lang.Integer) 110 */ 111 public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale) { 112 return null; 113 } 114 115 /** 116 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateAccountDistributionForProration(java.util.List, 117 * org.kuali.rice.kns.util.KualiDecimal, java.lang.Integer) 118 */ 119 public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale, Class clazz) { 120 String methodName = "generateAccountDistributionForProration()"; 121 if ( LOG.isDebugEnabled() ) { 122 LOG.debug(methodName + " started"); 123 } 124 List<PurApAccountingLine> newAccounts = new ArrayList(); 125 126 if (totalAmount.isZero()) { 127 throwRuntimeException(methodName, "Purchasing/Accounts Payable account distribution for proration does not allow zero dollar total."); 128 } 129 130 BigDecimal percentTotal = BigDecimal.ZERO; 131 BigDecimal totalAmountBigDecimal = totalAmount.bigDecimalValue(); 132 for (SourceAccountingLine accountingLine : accounts) { 133 if ( LOG.isDebugEnabled() ) { 134 LOG.debug(methodName + " " + accountingLine.getAccountNumber() + " " + accountingLine.getAmount() + "/" + totalAmountBigDecimal); 135 } 136 BigDecimal pct = accountingLine.getAmount().bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE); 137 pct = pct.stripTrailingZeros().multiply(ONE_HUNDRED); 138 139 if ( LOG.isDebugEnabled() ) { 140 LOG.debug(methodName + " pct = " + pct + " (trailing zeros removed)"); 141 } 142 143 BigDecimal lowestPossible = this.getLowestPossibleRoundUpNumber(); 144 if (lowestPossible.compareTo(pct) <= 0) { 145 PurApAccountingLine newAccountingLine; 146 newAccountingLine = null; 147 148 try { 149 newAccountingLine = (PurApAccountingLine) clazz.newInstance(); 150 } 151 catch (InstantiationException e) { 152 e.printStackTrace(); 153 } 154 catch (IllegalAccessException e) { 155 e.printStackTrace(); 156 } 157 158 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine); 159 newAccountingLine.setAccountLinePercent(pct); 160 if ( LOG.isDebugEnabled() ) { 161 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent()); 162 } 163 newAccounts.add(newAccountingLine); 164 percentTotal = percentTotal.add(newAccountingLine.getAccountLinePercent()); 165 if ( LOG.isDebugEnabled() ) { 166 LOG.debug(methodName + " total = " + percentTotal); 167 } 168 } 169 } 170 171 if ((percentTotal.compareTo(BigDecimal.ZERO)) == 0) { 172 /* 173 * This means there are so many accounts or so strange a distribution that we can't round properly... not sure of viable 174 * solution 175 */ 176 throwRuntimeException(methodName, "Can't round properly due to number of accounts"); 177 } 178 179 // Now deal with rounding 180 if ((ONE_HUNDRED.compareTo(percentTotal)) < 0) { 181 /* 182 * The total percent is greater than one hundred Here we find the account that occurs latest in our list with a percent 183 * that is higher than the difference and we subtract off the difference 184 */ 185 BigDecimal difference = percentTotal.subtract(ONE_HUNDRED); 186 if ( LOG.isDebugEnabled() ) { 187 LOG.debug(methodName + " Rounding up by " + difference); 188 } 189 190 boolean foundAccountToUse = false; 191 int currentNbr = newAccounts.size() - 1; 192 while (currentNbr >= 0) { 193 PurApAccountingLine potentialSlushAccount = (PurApAccountingLine) newAccounts.get(currentNbr); 194 if ((difference.compareTo(potentialSlushAccount.getAccountLinePercent())) < 0) { 195 // the difference amount is less than the current accounts percent... use this account 196 // the 'potentialSlushAccount' technically is now the true 'Slush Account' 197 potentialSlushAccount.setAccountLinePercent(potentialSlushAccount.getAccountLinePercent().subtract(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2)); 198 foundAccountToUse = true; 199 break; 200 } 201 currentNbr--; 202 } 203 204 if (!foundAccountToUse) { 205 /* 206 * We could not find any account in our list where the percent of that account was greater than that of the 207 * difference... doing so on just any account could result in a negative percent value 208 */ 209 throwRuntimeException(methodName, "Can't round properly due to math calculation error"); 210 } 211 212 } 213 else if ((ONE_HUNDRED.compareTo(percentTotal)) > 0) { 214 /* 215 * The total percent is less than one hundred Here we find the last account in our list and add the remaining required 216 * percent to its already calculated percent 217 */ 218 BigDecimal difference = ONE_HUNDRED.subtract(percentTotal); 219 if ( LOG.isDebugEnabled() ) { 220 LOG.debug(methodName + " Rounding down by " + difference); 221 } 222 PurApAccountingLine slushAccount = (PurApAccountingLine) newAccounts.get(newAccounts.size() - 1); 223 slushAccount.setAccountLinePercent(slushAccount.getAccountLinePercent().add(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2)); 224 } 225 if ( LOG.isDebugEnabled() ) { 226 LOG.debug(methodName + " ended"); 227 } 228 return newAccounts; 229 } 230 231 /** 232 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateAccountDistributionForProrationWithZeroTotal(java.util.List, 233 * java.lang.Integer) 234 */ 235 public List<PurApAccountingLine> generateAccountDistributionForProrationWithZeroTotal(PurchasingAccountsPayableDocument purapDoc) { 236 String methodName = "generateAccountDistributionForProrationWithZeroTotal()"; 237 if ( LOG.isDebugEnabled() ) { 238 LOG.debug(methodName + " started"); 239 } 240 241 List<PurApAccountingLine> accounts = generatePercentSummary(purapDoc); 242 243 // find the total percent and strip trailing zeros 244 BigDecimal totalPercentValue = BigDecimal.ZERO; 245 for (PurApAccountingLine accountingLine : accounts) { 246 totalPercentValue = totalPercentValue.add(accountingLine.getAccountLinePercent()).movePointLeft(2).stripTrailingZeros().movePointRight(2); 247 } 248 249 if ((BigDecimal.ZERO.compareTo(totalPercentValue.remainder(ONE_HUNDRED))) != 0) { 250 throwRuntimeException(methodName, "Invalid Percent Total of '" + totalPercentValue + "' does not allow for account distribution (must be multiple of 100)"); 251 } 252 253 List newAccounts = new ArrayList(); 254 BigDecimal logDisplayOnlyTotal = BigDecimal.ZERO; 255 BigDecimal percentUsed = BigDecimal.ZERO; 256 int accountListSize = accounts.size(); 257 int i = 0; 258 for (PurApAccountingLine accountingLine : accounts) { 259 i++; 260 BigDecimal percentToUse = BigDecimal.ZERO; 261 if ( LOG.isDebugEnabled() ) { 262 LOG.debug(methodName + " " + accountingLine.getChartOfAccountsCode() + "-" + accountingLine.getAccountNumber() + " " + accountingLine.getAmount() + "/" + percentToUse); 263 } 264 265 // if it's the last account make up the leftover percent 266 BigDecimal acctPercent = accountingLine.getAccountLinePercent(); 267 if ((i != accountListSize) || (accountListSize == 1)) { 268 // this account is not the last account or there is only one account 269 percentToUse = (acctPercent.divide(totalPercentValue, SCALE, BIG_DECIMAL_ROUNDING_MODE)).multiply(ONE_HUNDRED); 270 percentUsed = percentUsed.add(((acctPercent.divide(totalPercentValue, SCALE, BIG_DECIMAL_ROUNDING_MODE))).multiply(ONE_HUNDRED)); 271 } 272 else { 273 // this account is the last account so we have to makeup whatever is left out of 100 274 percentToUse = ONE_HUNDRED.subtract(percentUsed); 275 } 276 277 PurApAccountingLine newAccountingLine = accountingLine.createBlankAmountsCopy(); 278 if ( LOG.isDebugEnabled() ) { 279 LOG.debug(methodName + " pct = " + percentToUse); 280 } 281 newAccountingLine.setAccountLinePercent(percentToUse.setScale(accountingLine.getAccountLinePercent().scale(), BIG_DECIMAL_ROUNDING_MODE)); 282 if ( LOG.isDebugEnabled() ) { 283 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent()); 284 } 285 newAccounts.add(newAccountingLine); 286 logDisplayOnlyTotal = logDisplayOnlyTotal.add(newAccountingLine.getAccountLinePercent()); 287 if ( LOG.isDebugEnabled() ) { 288 LOG.debug(methodName + " total = " + logDisplayOnlyTotal); 289 } 290 } 291 if ( LOG.isDebugEnabled() ) { 292 LOG.debug(methodName + " ended"); 293 } 294 return newAccounts; 295 } 296 297 /** 298 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummary(java.util.List) 299 */ 300 public List<SourceAccountingLine> generateSummary(List<PurApItem> items) { 301 String methodName = "generateSummary()"; 302 if ( LOG.isDebugEnabled() ) { 303 LOG.debug(methodName + " started"); 304 } 305 List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 306 if ( LOG.isDebugEnabled() ) { 307 LOG.debug(methodName + " ended"); 308 } 309 return returnList; 310 } 311 312 public List<SourceAccountingLine> generateSummaryTaxableAccounts(List<PurApItem> items) { 313 String methodName = "generateSummary()"; 314 if ( LOG.isDebugEnabled() ) { 315 LOG.debug(methodName + " started"); 316 } 317 List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, true); 318 if ( LOG.isDebugEnabled() ) { 319 LOG.debug(methodName + " ended"); 320 } 321 return returnList; 322 } 323 324 /** 325 * 326 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryAccounts(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) 327 */ 328 public List<SummaryAccount> generateSummaryAccounts(PurchasingAccountsPayableDocument document) { 329 // always update the amounts first 330 updateAccountAmounts(document); 331 return generateSummaryAccounts(document.getItems(), ZERO_TOTALS_RETURNED_VALUE, USE_TAX_INCLUDED); 332 } 333 334 335 336 /** 337 * 338 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryAccountsWithNoZeroTotals(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) 339 */ 340 public List<SummaryAccount> generateSummaryAccountsWithNoZeroTotals(PurchasingAccountsPayableDocument document) { 341 // always update the amounts first 342 updateAccountAmounts(document); 343 return generateSummaryAccounts(document.getItems(), ZERO_TOTALS_NOT_RETURNED_VALUE, USE_TAX_INCLUDED); 344 } 345 346 /** 347 * 348 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryAccountsWithNoZeroTotals(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) 349 */ 350 public List<SummaryAccount> generateSummaryAccountsWithNoZeroTotalsNoUseTax(PurchasingAccountsPayableDocument document) { 351 // always update the amounts first 352 updateAccountAmounts(document); 353 return generateSummaryAccounts(document.getItems(), ZERO_TOTALS_NOT_RETURNED_VALUE, USE_TAX_EXCLUDED); 354 } 355 356 /** 357 * 358 * This creates summary accounts based on a list of items. 359 * @param items a list of PurAp Items. 360 * @return a list of summary accounts. 361 */ 362 protected List<SummaryAccount> generateSummaryAccounts(List<PurApItem> items, Boolean useZeroTotals, Boolean useTaxIncluded) { 363 String methodName = "generateSummaryAccounts()"; 364 List<SummaryAccount> returnList = new ArrayList<SummaryAccount>(); 365 if ( LOG.isDebugEnabled() ) { 366 LOG.debug(methodName + " started"); 367 } 368 369 List<SourceAccountingLine> sourceLines = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, useZeroTotals, ALTERNATE_AMOUNT_NOT_USED, useTaxIncluded, false); 370 for (SourceAccountingLine sourceAccountingLine : sourceLines) { 371 SummaryAccount summaryAccount = new SummaryAccount(); 372 summaryAccount.setAccount((SourceAccountingLine) ObjectUtils.deepCopy(sourceAccountingLine)); 373 for (PurApItem item : items) { 374 List<PurApAccountingLine> itemAccounts = item.getSourceAccountingLines(); 375 for (PurApAccountingLine purApAccountingLine : itemAccounts) { 376 if (purApAccountingLine.accountStringsAreEqual(summaryAccount.getAccount())) { 377 PurApSummaryItem summaryItem = item.getSummaryItem(); 378 //If the summaryItem is null, it means the item is not eligible to 379 //be displayed in the Account Summary tab. If it's not null then 380 //we'll set the estimatedEncumberanceAmount and add the item to the 381 //summaryAccount list to be displayed in the Account Summary tab. 382 if (summaryItem != null) { 383 summaryItem.setEstimatedEncumberanceAmount(purApAccountingLine.getAmount()); 384 summaryAccount.getItems().add(summaryItem); 385 break; 386 } 387 } 388 389 } 390 } 391 returnList.add(summaryAccount); 392 } 393 if ( LOG.isDebugEnabled() ) { 394 LOG.debug(methodName + " ended"); 395 } 396 return returnList; 397 } 398 399 /** 400 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryWithNoZeroTotals(java.util.List) 401 */ 402 public List<SourceAccountingLine> generateSummaryWithNoZeroTotals(List<PurApItem> items) { 403 String methodName = "generateSummaryWithNoZeroTotals()"; 404 if ( LOG.isDebugEnabled() ) { 405 LOG.debug(methodName + " started"); 406 } 407 List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 408 if ( LOG.isDebugEnabled() ) { 409 LOG.debug(methodName + " ended"); 410 } 411 return returnList; 412 } 413 414 /** 415 * calls generateSummary with no use tax included 416 */ 417 public List<SourceAccountingLine> generateSummaryWithNoZeroTotalsNoUseTax(List<PurApItem> items) { 418 String methodName = "generateSummaryWithNoZeroTotalsNoUseTax()"; 419 if ( LOG.isDebugEnabled() ) { 420 LOG.debug(methodName + " started"); 421 } 422 List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_EXCLUDED, false); 423 if ( LOG.isDebugEnabled() ) { 424 LOG.debug(methodName + " ended"); 425 } 426 427 return returnList; 428 } 429 430 /** 431 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryWithNoZeroTotalsUsingAlternateAmount(java.util.List) 432 */ 433 public List<SourceAccountingLine> generateSummaryWithNoZeroTotalsUsingAlternateAmount(List<PurApItem> items) { 434 String methodName = "generateSummaryWithNoZeroTotals()"; 435 if ( LOG.isDebugEnabled() ) { 436 LOG.debug(methodName + " started"); 437 } 438 List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_USED, USE_TAX_INCLUDED, false); 439 if ( LOG.isDebugEnabled() ) { 440 LOG.debug(methodName + " ended"); 441 } 442 return returnList; 443 } 444 445 /** 446 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryExcludeItemTypes(java.util.List, java.util.Set) 447 */ 448 public List<SourceAccountingLine> generateSummaryExcludeItemTypes(List<PurApItem> items, Set excludedItemTypeCodes) { 449 String methodName = "generateSummaryExcludeItemTypes()"; 450 if ( LOG.isDebugEnabled() ) { 451 LOG.debug(methodName + " started"); 452 } 453 List<SourceAccountingLine> returnList = generateAccountSummary(items, excludedItemTypeCodes, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 454 if ( LOG.isDebugEnabled() ) { 455 LOG.debug(methodName + " ended"); 456 } 457 return returnList; 458 } 459 460 /** 461 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryIncludeItemTypesAndNoZeroTotals(java.util.List, 462 * java.util.Set) 463 */ 464 public List<SourceAccountingLine> generateSummaryIncludeItemTypesAndNoZeroTotals(List<PurApItem> items, Set includedItemTypeCodes) { 465 String methodName = "generateSummaryExcludeItemTypesAndNoZeroTotals()"; 466 if ( LOG.isDebugEnabled() ) { 467 LOG.debug(methodName + " started"); 468 } 469 List<SourceAccountingLine> returnList = generateAccountSummary(items, includedItemTypeCodes, ITEM_TYPES_INCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 470 if ( LOG.isDebugEnabled() ) { 471 LOG.debug(methodName + " ended"); 472 } 473 return returnList; 474 } 475 476 /** 477 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryIncludeItemTypes(java.util.List, java.util.Set) 478 */ 479 public List<SourceAccountingLine> generateSummaryIncludeItemTypes(List<PurApItem> items, Set includedItemTypeCodes) { 480 String methodName = "generateSummaryIncludeItemTypes()"; 481 if ( LOG.isDebugEnabled() ) { 482 LOG.debug(methodName + " started"); 483 } 484 List<SourceAccountingLine> returnList = generateAccountSummary(items, includedItemTypeCodes, ITEM_TYPES_INCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 485 if ( LOG.isDebugEnabled() ) { 486 LOG.debug(methodName + " ended"); 487 } 488 return returnList; 489 } 490 491 /** 492 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateSummaryExcludeItemTypesAndNoZeroTotals(java.util.List, 493 * java.util.Set) 494 */ 495 public List<SourceAccountingLine> generateSummaryExcludeItemTypesAndNoZeroTotals(List<PurApItem> items, Set excludedItemTypeCodes) { 496 String methodName = "generateSummaryIncludeItemTypesAndNoZeroTotals()"; 497 if ( LOG.isDebugEnabled() ) { 498 LOG.debug(methodName + " started"); 499 } 500 List<SourceAccountingLine> returnList = generateAccountSummary(items, excludedItemTypeCodes, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false); 501 if ( LOG.isDebugEnabled() ) { 502 LOG.debug(methodName + " ended"); 503 } 504 return returnList; 505 } 506 507 /** 508 * Generates an account summary, that is it creates a list of source accounts 509 * by rounding up the purap accounts off of the purap items. 510 * @param items the items to determ 511 * @param itemTypeCodes the item types to determine whether to look at an item in combination with itemTypeCodesAreIncluded 512 * @param itemTypeCodesAreIncluded value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables 513 * @param useZeroTotals whether to include items with a zero dollar total 514 * @param useAlternateAmount an alternate amount used in certain cases for GL entry 515 * @return a list of source accounts 516 */ 517 protected List<SourceAccountingLine> generateAccountSummary(List<PurApItem> items, Set<String> itemTypeCodes, Boolean itemTypeCodesAreIncluded, 518 Boolean useZeroTotals, Boolean useAlternateAmount, Boolean useTaxIncluded, Boolean taxableOnly) { 519 List<PurApItem> itemsToProcess = getProcessablePurapItems(items, itemTypeCodes, itemTypeCodesAreIncluded, useZeroTotals); 520 Map<PurApAccountingLine,KualiDecimal> accountMap = new HashMap<PurApAccountingLine,KualiDecimal>(); 521 522 for (PurApItem currentItem : itemsToProcess) { 523 if (PurApItemUtils.checkItemActive(currentItem)) { 524 List<PurApAccountingLine> sourceAccountingLines = currentItem.getSourceAccountingLines(); 525 526 //skip if item is not taxable and taxable only flag has been set 527 if (taxableOnly) { 528 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument(); 529 if(!purapService.isTaxableForSummary(document.isUseTaxIndicator(), purapService.getDeliveryState(document), currentItem)){ 530 continue; 531 } 532 } 533 534 if (!useTaxIncluded) { 535 //if no use tax set the source accounting lines to a clone so we can update 536 //them to be based on the non tax amount 537 PurApItem cloneItem = (PurApItem)ObjectUtils.deepCopy(currentItem); 538 sourceAccountingLines = cloneItem.getSourceAccountingLines(); 539 updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount()); 540 } 541 542 for (PurApAccountingLine account : sourceAccountingLines) { 543 544 //skip account if not taxable and taxable only flag is set 545 if (taxableOnly) { 546 PurchasingAccountsPayableDocument document = currentItem.getPurapDocument(); 547 //check if account is not taxable, if not skip this account 548 if( !purapService.isAccountingLineTaxable(account, purapService.isDeliveryStateTaxable(purapService.getDeliveryState(document))) ){ 549 continue; 550 } 551 } 552 553 // getting the total to set on the account 554 KualiDecimal total = KualiDecimal.ZERO; 555 if (accountMap.containsKey(account)) { 556 total = accountMap.get(account); 557 } 558 559 if (useAlternateAmount) { 560 total = total.add(account.getAlternateAmountForGLEntryCreation()); 561 } 562 else { 563 total = total.add(account.getAmount()); 564 } 565 566 accountMap.put(account, total); 567 } 568 } 569 } 570 571 // convert list of PurApAccountingLine objects to SourceAccountingLineObjects 572 Iterator<PurApAccountingLine> iterator = accountMap.keySet().iterator(); 573 List<SourceAccountingLine> sourceAccounts = new ArrayList<SourceAccountingLine>(); 574 for (Iterator<PurApAccountingLine> iter = iterator; iter.hasNext();) { 575 PurApAccountingLine accountToConvert = (PurApAccountingLine) iter.next(); 576 if (accountToConvert.isEmpty()) { 577 String errorMessage = "Found an 'empty' account in summary generation " + accountToConvert.toString(); 578 LOG.error("generateAccountSummary() " + errorMessage); 579 throw new RuntimeException(errorMessage); 580 } 581 KualiDecimal sourceLineTotal = accountMap.get(accountToConvert); 582 SourceAccountingLine sourceLine = accountToConvert.generateSourceAccountingLine(); 583 sourceLine.setAmount(sourceLineTotal); 584 sourceAccounts.add(sourceLine); 585 } 586 587 // sort the sourceAccounts list first by account number, then by object code, ignoring chart code 588 Collections.sort(sourceAccounts, 589 new Comparator<SourceAccountingLine>() { 590 public int compare(SourceAccountingLine sal1, SourceAccountingLine sal2) { 591 int compare = 0; 592 if (sal1 != null && sal2 != null) { 593 if (sal1.getAccountNumber() != null && sal2.getAccountNumber() != null) { 594 compare = sal1.getAccountNumber().compareTo(sal2.getAccountNumber()); 595 if (compare == 0) { 596 if (sal1.getFinancialObjectCode() != null && sal2.getFinancialObjectCode() != null) 597 compare = sal1.getFinancialObjectCode().compareTo(sal2.getFinancialObjectCode()); 598 } 599 } 600 } 601 return compare; 602 } 603 } 604 ); 605 606 return sourceAccounts; 607 } 608 609 /** 610 * This method takes a list of {@link PurchasingApItem} objects and parses through them to see if each one should be processed 611 * according the the other variables passed in.<br> 612 * <br> 613 * Example 1:<br> 614 * items = "ITEM", "SITM", "FRHT", "SPHD"<br> 615 * itemTypeCodes = "FRHT"<br> 616 * itemTypeCodesAreIncluded = ITEM_TYPES_EXCLUDED_VALUE<br> 617 * return items "ITEM", "SITM", "FRHT", "SPHD"<br> 618 * <br> 619 * <br> 620 * Example 2:<br> 621 * items = "ITEM", "SITM", "FRHT", "SPHD"<br> 622 * itemTypeCodes = "ITEM","FRHT"<br> 623 * itemTypeCodesAreIncluded = ITEM_TYPES_INCLUDED_VALUE<br> 624 * return items "ITEM", "FRHT"<br> 625 * 626 * @param items - list of {@link PurchasingApItem} objects that need to be parsed 627 * @param itemTypeCodes - list of {@link org.kuali.kfs.module.purap.businessobject.ItemType} codes used in conjunction with 628 * itemTypeCodesAreIncluded parameter 629 * @param itemTypeCodesAreIncluded - value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables 630 * (see {@link #ITEM_TYPES_INCLUDED_VALUE}) 631 * @param useZeroTotals - value to tell whether to include zero dollar items (see {@link #ZERO_TOTALS_RETURNED_VALUE}) 632 * @return a list of {@link PurchasingApItem} objects that should be used for processing by calling method 633 */ 634 protected List<PurApItem> getProcessablePurapItems(List<PurApItem> items, Set itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals) { 635 String methodName = "getProcessablePurapItems()"; 636 List<PurApItem> newItemList = new ArrayList<PurApItem>(); 637 // error out if we have an invalid 'itemTypeCodesAreIncluded' value 638 if ((!(ITEM_TYPES_INCLUDED_VALUE.equals(itemTypeCodesAreIncluded))) && (!(ITEM_TYPES_EXCLUDED_VALUE.equals(itemTypeCodesAreIncluded)))) { 639 throwRuntimeException(methodName, "Invalid parameter found while trying to find processable items for dealing with purchasing/accounts payable accounts"); 640 } 641 for (PurApItem currentItem : items) { 642 if ((itemTypeCodes != null) && (!(itemTypeCodes.isEmpty()))) { 643 // we have at least one entry in our item type code list 644 boolean foundMatchInList = false; 645 // check to see if this item type code is in the list 646 for (Iterator iterator = itemTypeCodes.iterator(); iterator.hasNext();) { 647 String itemTypeCode = (String) iterator.next(); 648 // include this item if it's in the included list 649 if (itemTypeCode.equals(currentItem.getItemType().getItemTypeCode())) { 650 foundMatchInList = true; 651 break; 652 } 653 } 654 // check to see if item type code was found and if the list is describing included or excluded item types 655 if ((foundMatchInList) && (ITEM_TYPES_EXCLUDED_VALUE.equals(itemTypeCodesAreIncluded))) { 656 // this item type code is in the list 657 // this item type code is excluded so we skip it 658 continue; // skips current item 659 } 660 else if ((!foundMatchInList) && (ITEM_TYPES_INCLUDED_VALUE.equals(itemTypeCodesAreIncluded))) { 661 // this item type code is not in the list 662 // this item type code is not included so we skip it 663 continue; // skips current item 664 } 665 } 666 else { 667 // the item type code list is empty 668 if (ITEM_TYPES_INCLUDED_VALUE.equals(itemTypeCodesAreIncluded)) { 669 // the item type code list is empty and the list is supposed to contain the item types to include 670 throwRuntimeException(methodName, "Invalid parameter and list of items found while trying to find processable items for dealing with purchasing/accounts payable accounts"); 671 } 672 } 673 if ((ZERO_TOTALS_NOT_RETURNED_VALUE.equals(useZeroTotals)) && (ObjectUtils.isNull(currentItem.getExtendedPrice()) || ((KualiDecimal.ZERO.compareTo(currentItem.getExtendedPrice())) == 0))) { 674 // if we don't return zero dollar items then skip this one 675 continue; 676 } 677 newItemList.add(currentItem); 678 } 679 return newItemList; 680 } 681 682 /** 683 * 684 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#updateAccountAmounts(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) 685 */ 686 public void updateAccountAmounts(PurchasingAccountsPayableDocument document) { 687 // the percent at fiscal approve 688 // don't update if past the AP review level 689 if ((document instanceof PaymentRequestDocument) && purapService.isFullDocumentEntryCompleted(document)) { 690 //update the percent but don't update the amounts if preq and past full entry 691 convertMoneyToPercent((PaymentRequestDocument)document); 692 return; 693 } 694 document.fixItemReferences(); 695 for (PurApItem item : document.getItems()) { 696 updateItemAccountAmounts(item); 697 } 698 } 699 700 /** 701 * 702 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#updateItemAccountAmounts(org.kuali.kfs.module.purap.businessobject.PurApItem) 703 */ 704 public void updateItemAccountAmounts(PurApItem item) { 705 List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines(); 706 KualiDecimal totalAmount = item.getTotalAmount(); 707 708 updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount); 709 } 710 711 /** 712 * calculates values for a list of accounting lines based on an amount 713 * @param sourceAccountingLines 714 * @param totalAmount 715 */ 716 public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) { 717 if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) { 718 719 KualiDecimal accountTotal = KualiDecimal.ZERO; 720 T lastAccount = null; 721 722 723 for (T account : sourceAccountingLines) { 724 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) { 725 BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100)); 726 account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR))); 727 } 728 else { 729 account.setAmount(KualiDecimal.ZERO); 730 } 731 accountTotal = accountTotal.add(account.getAmount()); 732 lastAccount = account; 733 } 734 735 // put excess on last account 736 if (lastAccount != null) { 737 KualiDecimal difference = totalAmount.subtract(accountTotal); 738 lastAccount.setAmount(lastAccount.getAmount().add(difference)); 739 } 740 } 741 else { 742 // zero out if extended price is zero 743 for (T account : sourceAccountingLines) { 744 account.setAmount(KualiDecimal.ZERO); 745 } 746 } 747 } 748 749 public List<PurApAccountingLine> generatePercentSummary(PurchasingAccountsPayableDocument purapDoc) { 750 List<PurApAccountingLine> accounts = new ArrayList<PurApAccountingLine>(); 751 for (PurApItem currentItem : purapDoc.getItems()) { 752 if (PurApItemUtils.checkItemActive(currentItem)) { 753 for (PurApAccountingLine account : currentItem.getSourceAccountingLines()) { 754 boolean thisAccountAlreadyInSet = false; 755 for (Iterator iter = accounts.iterator(); iter.hasNext();) { 756 PurApAccountingLine alreadyAddedAccount = (PurApAccountingLine) iter.next(); 757 if (alreadyAddedAccount.accountStringsAreEqual(account)) { 758 759 alreadyAddedAccount.setAccountLinePercent(alreadyAddedAccount.getAccountLinePercent().add(account.getAccountLinePercent())); 760 761 thisAccountAlreadyInSet = true; 762 break; 763 } 764 } 765 if (!thisAccountAlreadyInSet) { 766 PurApAccountingLine accountToAdd = (PurApAccountingLine) ObjectUtils.deepCopy(account); 767 accounts.add(accountToAdd); 768 } 769 } 770 } 771 } 772 return accounts; 773 } 774 775 /** 776 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#convertMoneyToPercent(org.kuali.kfs.module.purap.document.PaymentRequestDocument) 777 */ 778 public void convertMoneyToPercent(PaymentRequestDocument pr) { 779 LOG.debug("convertMoneyToPercent() started"); 780 781 int itemNbr = 0; 782 783 for (Iterator<PaymentRequestItem> iter = pr.getItems().iterator(); iter.hasNext();) { 784 PaymentRequestItem item = (PaymentRequestItem) iter.next(); 785 786 itemNbr++; 787 String identifier = item.getItemIdentifierString(); 788 789 if (item.getTotalAmount()!=null && item.getTotalAmount().isNonZero()) { 790 int numOfAccounts = item.getSourceAccountingLines().size(); 791 BigDecimal percentTotal = BigDecimal.ZERO; 792 KualiDecimal accountTotal = KualiDecimal.ZERO; 793 int accountIdentifier = 0; 794 795 for (Iterator<PurApAccountingLine> iterator = item.getSourceAccountingLines().iterator(); iterator.hasNext();) { 796 accountIdentifier++; 797 PaymentRequestAccount account = (PaymentRequestAccount) iterator.next(); 798 799 //account.getAmount returns the wrong value for trade in source accounting lines... 800 KualiDecimal accountAmount = KualiDecimal.ZERO; 801 802 accountAmount = account.getAmount(); 803 804 BigDecimal tmpPercent = BigDecimal.ZERO; 805 KualiDecimal extendedPrice = item.getTotalAmount(); 806 tmpPercent = accountAmount.bigDecimalValue().divide(extendedPrice.bigDecimalValue(), PurapConstants.CREDITMEMO_PRORATION_SCALE.intValue(), KualiDecimal.ROUND_BEHAVIOR); 807 808 if (accountIdentifier == numOfAccounts) { 809 // if on last account, calculate the percent by subtracting current percent total from 1 810 tmpPercent = BigDecimal.ONE.subtract(percentTotal); 811 } 812 813 // test that the above amount is correct, if so just check that the total of all these matches the item total 814 BigDecimal calcAmountBd = tmpPercent.multiply(extendedPrice.bigDecimalValue()); 815 calcAmountBd = calcAmountBd.setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR); 816 KualiDecimal calcAmount = new KualiDecimal(calcAmountBd); 817 if (calcAmount.compareTo(accountAmount) != 0) { 818 // rounding error 819 LOG.debug("convertMoneyToPercent() Rounding error on " + account); 820 String param1 = identifier + "." + accountIdentifier; 821 String param2 = calcAmount.bigDecimalValue().subtract(accountAmount.bigDecimalValue()).toString(); 822 GlobalVariables.getMessageMap().putError(item.getItemIdentifierString(), PurapKeyConstants.ERROR_ITEM_ACCOUNTING_ROUNDING, param1, param2); 823 account.setAmount(calcAmount); 824 } 825 826 // update percent 827 LOG.debug("convertMoneyToPercent() updating percent to " + tmpPercent); 828 account.setAccountLinePercent(tmpPercent.multiply(new BigDecimal(100))); 829 830 // check total based on adjusted amount 831 accountTotal = accountTotal.add(calcAmount); 832 percentTotal = percentTotal.add(tmpPercent); 833 } 834 } 835 } 836 } 837 838 /** 839 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#deleteSummaryAccounts(java.lang.Integer, java.lang.String) 840 */ 841 public void deleteSummaryAccounts(Integer purapDocumentIdentifier, String docType) { 842 if (PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT.equals(docType)) { 843 purApAccountingDao.deleteSummaryAccountsbyPaymentRequestIdentifier(purapDocumentIdentifier); 844 } 845 else if (PurapDocTypeCodes.CREDIT_MEMO_DOCUMENT.equals(docType)) { 846 purApAccountingDao.deleteSummaryAccountsbyCreditMemoIdentifier(purapDocumentIdentifier); 847 } 848 } 849 850 public List getAccountsPayableSummaryAccounts(Integer purapDocumentIdentifier, String docType) { 851 if (PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT.equals(docType)) { 852 return purApAccountingDao.getSummaryAccountsbyPaymentRequestIdentifier(purapDocumentIdentifier); 853 } 854 else if (PurapDocTypeCodes.CREDIT_MEMO_DOCUMENT.equals(docType)) { 855 purApAccountingDao.getSummaryAccountsbyCreditMemoIdentifier(purapDocumentIdentifier); 856 } 857 return null; 858 } 859 860 public List<PurApAccountingLine> getAccountsFromItem(PurApItem item) { 861 return purApAccountingDao.getAccountingLinesForItem(item); 862 } 863 864 public List<SourceAccountingLine> generateSourceAccountsForVendorRemit(PurchasingAccountsPayableDocument document) { 865 //correct initial amounts or percents 866 updateAccountAmounts(document); 867 List<SourceAccountingLine> vendorSummaryAccounts = new ArrayList<SourceAccountingLine>(); 868 869 //update accounts here with amounts to send to vendor 870 vendorSummaryAccounts = generateSummaryWithNoZeroTotalsNoUseTax(document.getItems()); 871 872 return vendorSummaryAccounts; 873 } 874 875 /** 876 * 877 * gets sum total of accounts 878 * @param accounts 879 * @return 880 */ 881 882 protected KualiDecimal calculateSumTotal(List<SourceAccountingLine> accounts) { 883 KualiDecimal total = KualiDecimal.ZERO; 884 for (SourceAccountingLine accountingLine : accounts) { 885 total = total.add( accountingLine.getAmount()); 886 } 887 return total; 888 } 889 890 891 892 /** 893 * 894 * Replaces amount field with prorated tax amount in list 895 * @param accounts list of accounts 896 * @param useTax tax to be allocated to these accounts 897 * @param newSourceLines rewrites the source account lines 898 */ 899 900 protected void convertAmtToTax(List<PurApAccountingLine> accounts, KualiDecimal useTax, List<SourceAccountingLine> newSourceLines) { 901 final BigDecimal HUNDRED = new BigDecimal(100); 902 PurApAccountingLine purApAccountingLine; 903 BigDecimal proratedAmtBD; 904 KualiDecimal proratedAmt; 905 //convert back to source 906 KualiDecimal total = KualiDecimal.ZERO; 907 int last = accounts.size() - 1; 908 for (int i = 0; i < last; i++) { 909 purApAccountingLine = accounts.get(i); 910 proratedAmtBD = useTax.bigDecimalValue().multiply(purApAccountingLine.getAccountLinePercent()); 911 // last object takes the rest of the amount 912 //proratedAmt = (accounts.indexOf(purApAccountingLine) == last) ? useTax.subtract(total) : proratedAmt.divide(HUNDRED); 913 proratedAmtBD = proratedAmtBD.divide(HUNDRED); 914 proratedAmt = new KualiDecimal(proratedAmtBD); 915 SourceAccountingLine acctLine = purApAccountingLine.generateSourceAccountingLine(); 916 acctLine.setAmount(proratedAmt); 917 newSourceLines.add(acctLine); 918 total = total.add(proratedAmt); 919 } 920 // update last object with remaining balance 921 proratedAmt = useTax.subtract(total); 922 purApAccountingLine = accounts.get(last); 923 SourceAccountingLine acctLine = purApAccountingLine.generateSourceAccountingLine(); 924 acctLine.setAmount(proratedAmt); 925 newSourceLines.add(acctLine); 926 } 927 928 /** 929 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#generateUseTaxAccount(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) 930 */ 931 public List<UseTaxContainer> generateUseTaxAccount(PurchasingAccountsPayableDocument document) { 932 List<UseTaxContainer> useTaxAccounts = new ArrayList<UseTaxContainer>(); 933 934 HashMap<PurApItemUseTax,UseTaxContainer> useTaxItemMap = new HashMap<PurApItemUseTax,UseTaxContainer>(); 935 Class accountingLineClass = null; 936 if(!document.isUseTaxIndicator()) { 937 //not useTax, return 938 return useTaxAccounts; 939 } 940 for (PurApItem purApItem : document.getItems()) { 941 if(!purApItem.getUseTaxItems().isEmpty()) { 942 if(accountingLineClass==null) { 943 accountingLineClass = purApItem.getAccountingLineClass(); 944 } 945 UseTaxContainer useTaxContainer=new UseTaxContainer(); 946 for (PurApItemUseTax itemUseTax : purApItem.getUseTaxItems()) { 947 if(useTaxItemMap.containsKey(itemUseTax)) { 948 useTaxContainer = useTaxItemMap.get(itemUseTax); 949 PurApItemUseTax exisitingItemUseTax = useTaxContainer.getUseTax(); 950 //if already in set we need to add on the old amount 951 KualiDecimal tax = exisitingItemUseTax.getTaxAmount(); 952 tax = tax.add(itemUseTax.getTaxAmount()); 953 exisitingItemUseTax.setTaxAmount(tax); 954 955 List<PurApItem> items = useTaxContainer.getItems(); 956 items.add(purApItem); 957 useTaxContainer.setItems(items); 958 959 } else { 960 useTaxContainer = new UseTaxContainer(itemUseTax,purApItem); 961 useTaxItemMap.put(itemUseTax, useTaxContainer); 962 useTaxAccounts.add(useTaxContainer); 963 } 964 } 965 } 966 } 967 // iterate over useTaxAccounts and set summary accounts using proration 968 for (UseTaxContainer useTaxContainer : useTaxAccounts) { 969 970 //create summary from items 971 List<SourceAccountingLine> origSourceAccounts = this.generateSummaryWithNoZeroTotals(useTaxContainer.getItems()); 972 KualiDecimal totalAmount = calculateSumTotal(origSourceAccounts); 973 List<PurApAccountingLine> accountingLines = generateAccountDistributionForProration(origSourceAccounts, totalAmount, PurapConstants.PRORATION_SCALE, 974 accountingLineClass); 975 976 977 978 List<SourceAccountingLine> newSourceLines = new ArrayList<SourceAccountingLine>(); 979 //convert back to source 980 convertAmtToTax(accountingLines, useTaxContainer.getUseTax().getTaxAmount(), newSourceLines); 981 982 //do we need an update accounts here? 983 useTaxContainer.setAccounts(newSourceLines); 984 } 985 986 useTaxAccounts=new ArrayList<UseTaxContainer>(useTaxItemMap.values()); 987 return useTaxAccounts; 988 } 989 990 /** 991 * @see org.kuali.kfs.module.purap.service.PurapAccountingService#isTaxAccount(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument, org.kuali.kfs.sys.businessobject.SourceAccountingLine) 992 */ 993 public boolean isTaxAccount(PurchasingAccountsPayableDocument document, SourceAccountingLine account) { 994 boolean isTaxAccount = false; 995 996 // check if the summary account is for tax withholding 997 if (document instanceof PaymentRequestDocument) { 998 String incomeClassCode = ((PaymentRequestDocument)document).getTaxClassificationCode(); 999 if (StringUtils.isNotEmpty(incomeClassCode)) { 1000 1001 String federalChartCode = parameterService.getParameterValue(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX); 1002 String federalAccountNumber = parameterService.getParameterValue(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX); 1003 String federalObjectCode = parameterService.getParameterValue(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, incomeClassCode); 1004 1005 String stateChartCode = parameterService.getParameterValue(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX); 1006 String stateAccountNumber = parameterService.getParameterValue(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX); 1007 String stateObjectCode = parameterService.getParameterValue(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, incomeClassCode); 1008 1009 String chartCode = account.getChartOfAccountsCode(); 1010 String accountNumber = account.getAccountNumber(); 1011 String objectCode = account.getFinancialObjectCode(); 1012 1013 boolean isFederalAccount = StringUtils.equals(federalChartCode, chartCode); 1014 isFederalAccount = isFederalAccount && StringUtils.equals(federalAccountNumber, accountNumber); 1015 isFederalAccount = isFederalAccount && StringUtils.equals(federalObjectCode, objectCode); 1016 1017 boolean isStateAccount = StringUtils.equals(stateChartCode, chartCode); 1018 isStateAccount = isStateAccount && StringUtils.equals(stateAccountNumber, accountNumber); 1019 isStateAccount = isStateAccount && StringUtils.equals(stateObjectCode, objectCode); 1020 1021 isTaxAccount = isFederalAccount || isStateAccount; 1022 } 1023 } 1024 1025 return isTaxAccount; 1026 } 1027 1028 public void setParameterService(ParameterService parameterService) { 1029 this.parameterService = parameterService; 1030 } 1031 1032 public void setPurApAccountingDao(PurApAccountingDao purApAccountingDao) { 1033 this.purApAccountingDao = purApAccountingDao; 1034 } 1035 1036 public void setPurapService(PurapService purapService) { 1037 this.purapService = purapService; 1038 } 1039 1040 public List<SourceAccountingLine> mergeAccountingLineLists(List<SourceAccountingLine> accountingLines1, List<SourceAccountingLine> accountingLines2){ 1041 1042 KualiDecimal totalAmount = KualiDecimal.ZERO; 1043 List<SourceAccountingLine> mergedAccountList = new ArrayList(); 1044 1045 for(SourceAccountingLine line1 : accountingLines1){ 1046 1047 for(SourceAccountingLine line2 : accountingLines2){ 1048 //if we find a match between lists, then merge amounts 1049 if(line1.equals(line2)){ 1050 //add the two amounts 1051 totalAmount = line1.getAmount().add(line2.getAmount()); 1052 line1.setAmount(totalAmount); 1053 } 1054 } 1055 1056 mergedAccountList.add(line1); 1057 } 1058 1059 return mergedAccountList; 1060 } 1061 1062 }