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