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 }