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.module.purap.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.ole.module.purap.PurapConstants;
20  import org.kuali.ole.module.purap.PurapConstants.PurapDocTypeCodes;
21  import org.kuali.ole.module.purap.PurapKeyConstants;
22  import org.kuali.ole.module.purap.PurapParameterConstants.NRATaxParameters;
23  import org.kuali.ole.module.purap.PurapPropertyConstants;
24  import org.kuali.ole.module.purap.businessobject.*;
25  import org.kuali.ole.module.purap.dataaccess.PurApAccountingDao;
26  import org.kuali.ole.module.purap.document.InvoiceDocument;
27  import org.kuali.ole.module.purap.document.PaymentRequestDocument;
28  import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument;
29  import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocumentBase;
30  import org.kuali.ole.module.purap.document.service.PurapService;
31  import org.kuali.ole.module.purap.service.PurapAccountingService;
32  import org.kuali.ole.module.purap.util.PurApItemUtils;
33  import org.kuali.ole.module.purap.util.PurApObjectUtils;
34  import org.kuali.ole.module.purap.util.SummaryAccount;
35  import org.kuali.ole.module.purap.util.UseTaxContainer;
36  import org.kuali.ole.select.businessobject.*;
37  import org.kuali.ole.select.document.OleInvoiceDocument;
38  import org.kuali.ole.select.document.OlePaymentRequestDocument;
39  import org.kuali.ole.select.document.OleVendorCreditMemoDocument;
40  import org.kuali.ole.sys.businessobject.AccountingLineBase;
41  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
42  import org.kuali.ole.sys.context.SpringContext;
43  import org.kuali.ole.sys.service.NonTransactional;
44  import org.kuali.rice.core.api.util.type.KualiDecimal;
45  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
46  import org.kuali.rice.krad.service.BusinessObjectService;
47  import org.kuali.rice.krad.service.KualiRuleService;
48  import org.kuali.rice.krad.util.GlobalVariables;
49  import org.kuali.rice.krad.util.ObjectUtils;
50  
51  import java.math.BigDecimal;
52  import java.util.*;
53  
54  /**
55   * Contains a number of helper methods to deal with accounts on Purchasing Accounts Payable Documents
56   */
57  
58  @NonTransactional
59  public class PurapAccountingServiceImpl implements PurapAccountingService {
60      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapAccountingServiceImpl.class);
61  
62      protected static final BigDecimal ONE_HUNDRED = new BigDecimal(100);
63      protected static final int SCALE = 340;
64      protected static final int BIG_DECIMAL_ROUNDING_MODE = BigDecimal.ROUND_HALF_UP;
65      protected static final int BIG_DECIMAL_SCALE = 2;
66  
67      // local constants
68      protected static final Boolean ITEM_TYPES_INCLUDED_VALUE = Boolean.TRUE;
69      ;
70      protected static final Boolean ITEM_TYPES_EXCLUDED_VALUE = Boolean.FALSE;
71      protected static final Boolean ZERO_TOTALS_RETURNED_VALUE = Boolean.TRUE;
72      protected static final Boolean ZERO_TOTALS_NOT_RETURNED_VALUE = Boolean.FALSE;
73      protected static final Boolean ALTERNATE_AMOUNT_USED = Boolean.TRUE;
74      protected static final Boolean ALTERNATE_AMOUNT_NOT_USED = Boolean.FALSE;
75      protected static final Boolean USE_TAX_INCLUDED = Boolean.TRUE;
76      protected static final Boolean USE_TAX_EXCLUDED = Boolean.FALSE;
77  
78      protected ParameterService parameterService;
79      protected PurapService purapService;
80      protected PurApAccountingDao purApAccountingDao;
81      protected BusinessObjectService businessObjectService;
82      private OlePaymentRequestDocument olePaymentRequestDocument;
83  
84      /**
85       * gets the lowest possible number for rounding, it works for ROUND_HALF_UP
86       *
87       * @return a BigDecimal representing the lowest possible number for rounding
88       */
89      protected BigDecimal getLowestPossibleRoundUpNumber() {
90          BigDecimal startingDigit = new BigDecimal(0.5);
91          if (SCALE != 0) {
92              startingDigit = startingDigit.movePointLeft(SCALE);
93          }
94          return startingDigit;
95      }
96  
97      /**
98       * Helper method to log and throw an error
99       *
100      * @param methodName   the method it's coming from
101      * @param errorMessage the actual error
102      */
103     protected void throwRuntimeException(String methodName, String errorMessage) {
104         LOG.error(methodName + "  " + errorMessage);
105         throw new RuntimeException(errorMessage);
106     }
107 
108     /**
109      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateAccountDistributionForProration(java.util.List,
110      *      org.kuali.rice.kns.util.KualiDecimal, java.lang.Integer)
111      * @deprecated
112      */
113     @Deprecated
114     @Override
115     public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale) {
116         return null;
117     }
118 
119     /**
120      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateAccountDistributionForProration(java.util.List,
121      *      org.kuali.rice.kns.util.KualiDecimal, java.lang.Integer)
122      */
123     @Override
124     public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale, Class clazz) {
125         String methodName = "generateAccountDistributionForProration()";
126         if (LOG.isDebugEnabled()) {
127             LOG.debug(methodName + " started");
128         }
129         List<PurApAccountingLine> newAccounts = new ArrayList();
130 
131         if (totalAmount.isZero()) {
132             throwRuntimeException(methodName, "Purchasing/Accounts Payable account distribution for proration does not allow zero dollar total.");
133         }
134 
135         BigDecimal percentTotal = BigDecimal.ZERO;
136         BigDecimal totalAmountBigDecimal = totalAmount.bigDecimalValue();
137         for (SourceAccountingLine accountingLine : accounts) {
138             KualiDecimal amt = KualiDecimal.ZERO;
139             if (ObjectUtils.isNotNull(accountingLine.getAmount())) {
140                 amt = accountingLine.getAmount();
141             }
142 
143             if (LOG.isDebugEnabled()) {
144                 LOG.debug(methodName + " " + accountingLine.getAccountNumber() + " " + amt + "/" + totalAmountBigDecimal);
145             }
146             BigDecimal pct = amt.bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE);
147             pct = pct.stripTrailingZeros().multiply(ONE_HUNDRED);
148 
149             if (LOG.isDebugEnabled()) {
150                 LOG.debug(methodName + " pct = " + pct + "  (trailing zeros removed)");
151             }
152 
153             BigDecimal lowestPossible = this.getLowestPossibleRoundUpNumber();
154             if (lowestPossible.compareTo(pct) <= 0) {
155                 PurApAccountingLine newAccountingLine;
156                 newAccountingLine = null;
157 
158                 try {
159                     newAccountingLine = (PurApAccountingLine) clazz.newInstance();
160                 } catch (InstantiationException e) {
161                     e.printStackTrace();
162                 } catch (IllegalAccessException e) {
163                     e.printStackTrace();
164                 }
165 
166                 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine);
167                 newAccountingLine.setAccountLinePercent(pct);
168                 if (LOG.isDebugEnabled()) {
169                     LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent());
170                 }
171                 newAccounts.add(newAccountingLine);
172                 percentTotal = percentTotal.add(newAccountingLine.getAccountLinePercent());
173                 if (LOG.isDebugEnabled()) {
174                     LOG.debug(methodName + " total = " + percentTotal);
175                 }
176             }
177         }
178 
179         if ((percentTotal.compareTo(BigDecimal.ZERO)) == 0) {
180             /*
181              * This means there are so many accounts or so strange a distribution that we can't round properly... not sure of viable
182              * solution
183              */
184             throwRuntimeException(methodName, "Can't round properly due to number of accounts");
185         }
186 
187         // Now deal with rounding
188         if ((ONE_HUNDRED.compareTo(percentTotal)) < 0) {
189             /*
190              * The total percent is greater than one hundred Here we find the account that occurs latest in our list with a percent
191              * that is higher than the difference and we subtract off the difference
192              */
193             BigDecimal difference = percentTotal.subtract(ONE_HUNDRED);
194             if (LOG.isDebugEnabled()) {
195                 LOG.debug(methodName + " Rounding up by " + difference);
196             }
197 
198             boolean foundAccountToUse = false;
199             int currentNbr = newAccounts.size() - 1;
200             while (currentNbr >= 0) {
201                 PurApAccountingLine potentialSlushAccount = newAccounts.get(currentNbr);
202 
203                 BigDecimal linePercent = BigDecimal.ZERO;
204                 if (ObjectUtils.isNotNull(potentialSlushAccount.getAccountLinePercent())) {
205                     linePercent = potentialSlushAccount.getAccountLinePercent();
206                 }
207 
208                 if ((difference.compareTo(linePercent)) < 0) {
209                     // the difference amount is less than the current accounts percent... use this account
210                     // the 'potentialSlushAccount' technically is now the true 'Slush Account'
211                     potentialSlushAccount.setAccountLinePercent(linePercent.subtract(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2));
212                     foundAccountToUse = true;
213                     break;
214                 }
215                 currentNbr--;
216             }
217 
218             if (!foundAccountToUse) {
219                 /*
220                  * We could not find any account in our list where the percent of that account was greater than that of the
221                  * difference... doing so on just any account could result in a negative percent value
222                  */
223                 throwRuntimeException(methodName, "Can't round properly due to math calculation error");
224             }
225 
226         } else if ((ONE_HUNDRED.compareTo(percentTotal)) > 0) {
227             /*
228              * The total percent is less than one hundred Here we find the last account in our list and add the remaining required
229              * percent to its already calculated percent
230              */
231             BigDecimal difference = ONE_HUNDRED.subtract(percentTotal);
232             if (LOG.isDebugEnabled()) {
233                 LOG.debug(methodName + " Rounding down by " + difference);
234             }
235             PurApAccountingLine slushAccount = newAccounts.get(newAccounts.size() - 1);
236 
237             BigDecimal slushLinePercent = BigDecimal.ZERO;
238             if (ObjectUtils.isNotNull(slushAccount.getAccountLinePercent())) {
239                 slushLinePercent = slushAccount.getAccountLinePercent();
240             }
241 
242             slushAccount.setAccountLinePercent(slushLinePercent.add(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2));
243         }
244         if (LOG.isDebugEnabled()) {
245             LOG.debug(methodName + " ended");
246         }
247         return newAccounts;
248     }
249 
250     /**
251      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateAccountDistributionForProrationWithZeroTotal(java.util.List,
252      *      java.lang.Integer)
253      */
254     @Override
255     public List<PurApAccountingLine> generateAccountDistributionForProrationWithZeroTotal(PurchasingAccountsPayableDocument purapDoc) {
256         String methodName = "generateAccountDistributionForProrationWithZeroTotal()";
257         if (LOG.isDebugEnabled()) {
258             LOG.debug(methodName + " started");
259         }
260 
261         List<PurApAccountingLine> accounts = generatePercentSummary(purapDoc);
262 
263         // find the total percent and strip trailing zeros
264         BigDecimal totalPercentValue = BigDecimal.ZERO;
265         for (PurApAccountingLine accountingLine : accounts) {
266             BigDecimal linePercent = BigDecimal.ZERO;
267             if (ObjectUtils.isNotNull(accountingLine.getAccountLinePercent())) {
268                 linePercent = accountingLine.getAccountLinePercent();
269             }
270 
271             totalPercentValue = totalPercentValue.add(linePercent).movePointLeft(2).stripTrailingZeros().movePointRight(2);
272         }
273 
274         if ((BigDecimal.ZERO.compareTo(totalPercentValue.remainder(ONE_HUNDRED))) != 0) {
275             throwRuntimeException(methodName, "Invalid Percent Total of '" + totalPercentValue + "' does not allow for account distribution (must be multiple of 100)");
276         }
277 
278         List newAccounts = new ArrayList();
279         BigDecimal logDisplayOnlyTotal = BigDecimal.ZERO;
280         BigDecimal percentUsed = BigDecimal.ZERO;
281         int accountListSize = accounts.size();
282         int i = 0;
283         for (PurApAccountingLine accountingLine : accounts) {
284             i++;
285             BigDecimal percentToUse = BigDecimal.ZERO;
286             KualiDecimal amt = KualiDecimal.ZERO;
287 
288             if (ObjectUtils.isNotNull(accountingLine.getAmount())) {
289                 amt = accountingLine.getAmount();
290             }
291 
292             if (LOG.isDebugEnabled()) {
293                 LOG.debug(methodName + " " + accountingLine.getChartOfAccountsCode() + "-" + accountingLine.getAccountNumber() + " " + amt + "/" + percentToUse);
294             }
295 
296             // if it's the last account make up the leftover percent
297             BigDecimal acctPercent = BigDecimal.ZERO;
298             if (ObjectUtils.isNotNull(accountingLine.getAccountLinePercent())) {
299                 acctPercent = accountingLine.getAccountLinePercent();
300             }
301 
302             if ((i != accountListSize) || (accountListSize == 1)) {
303                 // this account is not the last account or there is only one account
304                 percentToUse = (acctPercent.divide(totalPercentValue, SCALE, BIG_DECIMAL_ROUNDING_MODE)).multiply(ONE_HUNDRED);
305                 percentUsed = percentUsed.add(((acctPercent.divide(totalPercentValue, SCALE, BIG_DECIMAL_ROUNDING_MODE))).multiply(ONE_HUNDRED));
306             } else {
307                 // this account is the last account so we have to makeup whatever is left out of 100
308                 percentToUse = ONE_HUNDRED.subtract(percentUsed);
309             }
310 
311             PurApAccountingLine newAccountingLine = accountingLine.createBlankAmountsCopy();
312             if (LOG.isDebugEnabled()) {
313                 LOG.debug(methodName + " pct = " + percentToUse);
314             }
315             newAccountingLine.setAccountLinePercent(percentToUse.setScale(acctPercent.scale(), BIG_DECIMAL_ROUNDING_MODE));
316             if (LOG.isDebugEnabled()) {
317                 LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent());
318             }
319             newAccounts.add(newAccountingLine);
320             logDisplayOnlyTotal = logDisplayOnlyTotal.add(newAccountingLine.getAccountLinePercent());
321             if (LOG.isDebugEnabled()) {
322                 LOG.debug(methodName + " total = " + logDisplayOnlyTotal);
323             }
324         }
325         if (LOG.isDebugEnabled()) {
326             LOG.debug(methodName + " ended");
327         }
328         return newAccounts;
329     }
330 
331     /**
332      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummary(java.util.List)
333      */
334     @Override
335     public List<SourceAccountingLine> generateSummary(List<PurApItem> items) {
336         String methodName = "generateSummary()";
337         if (LOG.isDebugEnabled()) {
338             LOG.debug(methodName + " started");
339         }
340         List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false);
341         if (LOG.isDebugEnabled()) {
342             LOG.debug(methodName + " ended");
343         }
344         return returnList;
345     }
346 
347     @Override
348     public List<SourceAccountingLine> generateSummaryTaxableAccounts(List<PurApItem> items) {
349         String methodName = "generateSummary()";
350         if (LOG.isDebugEnabled()) {
351             LOG.debug(methodName + " started");
352         }
353         List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, true);
354         if (LOG.isDebugEnabled()) {
355             LOG.debug(methodName + " ended");
356         }
357         return returnList;
358     }
359 
360     /**
361      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryAccounts(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument)
362      */
363     @Override
364     public List<SummaryAccount> generateSummaryAccounts(PurchasingAccountsPayableDocument document) {
365         // always update the amounts first
366         updateAccountAmounts(document);
367         return generateSummaryAccounts(document.getItems(), ZERO_TOTALS_RETURNED_VALUE, USE_TAX_INCLUDED);
368     }
369 
370 
371     /**
372      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryAccountsWithNoZeroTotals(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument)
373      */
374     @Override
375     public List<SummaryAccount> generateSummaryAccountsWithNoZeroTotals(PurchasingAccountsPayableDocument document) {
376         // always update the amounts first
377         updateAccountAmounts(document);
378         return generateSummaryAccounts(document.getItems(), ZERO_TOTALS_NOT_RETURNED_VALUE, USE_TAX_INCLUDED);
379     }
380 
381     /**
382      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryAccountsWithNoZeroTotals(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument)
383      */
384     @Override
385     public List<SummaryAccount> generateSummaryAccountsWithNoZeroTotalsNoUseTax(PurchasingAccountsPayableDocument document) {
386         // always update the amounts first
387         updateAccountAmounts(document);
388         return generateSummaryAccounts(document.getItems(), ZERO_TOTALS_NOT_RETURNED_VALUE, USE_TAX_EXCLUDED);
389     }
390 
391     /**
392      * This creates summary accounts based on a list of items.
393      *
394      * @param items a list of PurAp Items.
395      * @return a list of summary accounts.
396      */
397     protected List<SummaryAccount> generateSummaryAccounts(List<PurApItem> items, Boolean useZeroTotals, Boolean useTaxIncluded) {
398         String methodName = "generateSummaryAccounts()";
399         List<SummaryAccount> returnList = new ArrayList<SummaryAccount>();
400         if (LOG.isDebugEnabled()) {
401             LOG.debug(methodName + " started");
402         }
403 
404         List<SourceAccountingLine> sourceLines = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, useZeroTotals, ALTERNATE_AMOUNT_NOT_USED, useTaxIncluded, false);
405         for (SourceAccountingLine sourceAccountingLine : sourceLines) {
406             SummaryAccount summaryAccount = new SummaryAccount();
407             summaryAccount.setAccount((SourceAccountingLine) ObjectUtils.deepCopy(sourceAccountingLine));
408             for (PurApItem item : items) {
409                 List<PurApAccountingLine> itemAccounts = item.getSourceAccountingLines();
410                 for (PurApAccountingLine purApAccountingLine : itemAccounts) {
411                     if (purApAccountingLine.accountStringsAreEqual(summaryAccount.getAccount())) {
412                         PurApSummaryItem summaryItem = item.getSummaryItem();
413                         // If the summaryItem is null, it means the item is not eligible to
414                         // be displayed in the Account Summary tab. If it's not null then
415                         // we'll set the estimatedEncumberanceAmount and add the item to the
416                         // summaryAccount list to be displayed in the Account Summary tab.
417                         KualiDecimal amt = KualiDecimal.ZERO;
418                         if (ObjectUtils.isNotNull(purApAccountingLine.getAmount())) {
419                             amt = purApAccountingLine.getAmount();
420                         }
421                         if (summaryItem != null) {
422                             if(item instanceof OleInvoiceItem) {
423                                 OleInvoiceItem oleInvoiceItem = (OleInvoiceItem) item;
424                                 if(!((OleInvoiceItem) item).isDebitItem()) {
425                                     summaryItem.setEstimatedEncumberanceAmount(amt.negated());
426                                 }
427                                 else {
428                                     summaryItem.setEstimatedEncumberanceAmount(amt);
429                                 }
430                             }
431                             else {
432                             summaryItem.setEstimatedEncumberanceAmount(amt);
433                             }
434                             summaryAccount.getItems().add(summaryItem);
435                             break;
436                         }
437                     }
438 
439                 }
440             }
441             returnList.add(summaryAccount);
442         }
443         if (LOG.isDebugEnabled()) {
444             LOG.debug(methodName + " ended");
445         }
446         return returnList;
447     }
448 
449     /**
450      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryWithNoZeroTotals(java.util.List)
451      */
452     @Override
453     public List<SourceAccountingLine> generateSummaryWithNoZeroTotals(List<PurApItem> items) {
454         String methodName = "generateSummaryWithNoZeroTotals()";
455         if (LOG.isDebugEnabled()) {
456             LOG.debug(methodName + " started");
457         }
458         List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false);
459         if (LOG.isDebugEnabled()) {
460             LOG.debug(methodName + " ended");
461         }
462         return returnList;
463     }
464 
465     /**
466      * calls generateSummary with no use tax included
467      */
468     @Override
469     public List<SourceAccountingLine> generateSummaryWithNoZeroTotalsNoUseTax(List<PurApItem> items) {
470         String methodName = "generateSummaryWithNoZeroTotalsNoUseTax()";
471         if (LOG.isDebugEnabled()) {
472             LOG.debug(methodName + " started");
473         }
474         List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_EXCLUDED, false);
475         if (LOG.isDebugEnabled()) {
476             LOG.debug(methodName + " ended");
477         }
478 
479         return returnList;
480     }
481 
482     /**
483      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryWithNoZeroTotalsUsingAlternateAmount(java.util.List)
484      */
485     @Override
486     public List<SourceAccountingLine> generateSummaryWithNoZeroTotalsUsingAlternateAmount(List<PurApItem> items) {
487         String methodName = "generateSummaryWithNoZeroTotals()";
488         if (LOG.isDebugEnabled()) {
489             LOG.debug(methodName + " started");
490         }
491         List<SourceAccountingLine> returnList = generateAccountSummary(items, null, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_USED, USE_TAX_INCLUDED, false);
492         if (LOG.isDebugEnabled()) {
493             LOG.debug(methodName + " ended");
494         }
495         return returnList;
496     }
497 
498     /**
499      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryExcludeItemTypes(java.util.List, java.util.Set)
500      */
501     @Override
502     public List<SourceAccountingLine> generateSummaryExcludeItemTypes(List<PurApItem> items, Set excludedItemTypeCodes) {
503         String methodName = "generateSummaryExcludeItemTypes()";
504         if (LOG.isDebugEnabled()) {
505             LOG.debug(methodName + " started");
506         }
507         List<SourceAccountingLine> returnList = generateAccountSummary(items, excludedItemTypeCodes, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false);
508         if (LOG.isDebugEnabled()) {
509             LOG.debug(methodName + " ended");
510         }
511         return returnList;
512     }
513 
514     /**
515      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryIncludeItemTypesAndNoZeroTotals(java.util.List,
516      *      java.util.Set)
517      */
518     @Override
519     public List<SourceAccountingLine> generateSummaryIncludeItemTypesAndNoZeroTotals(List<PurApItem> items, Set includedItemTypeCodes) {
520         String methodName = "generateSummaryExcludeItemTypesAndNoZeroTotals()";
521         if (LOG.isDebugEnabled()) {
522             LOG.debug(methodName + " started");
523         }
524         List<SourceAccountingLine> returnList = generateAccountSummary(items, includedItemTypeCodes, ITEM_TYPES_INCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false);
525         if (LOG.isDebugEnabled()) {
526             LOG.debug(methodName + " ended");
527         }
528         return returnList;
529     }
530 
531     /**
532      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryIncludeItemTypes(java.util.List, java.util.Set)
533      */
534     @Override
535     public List<SourceAccountingLine> generateSummaryIncludeItemTypes(List<PurApItem> items, Set includedItemTypeCodes) {
536         String methodName = "generateSummaryIncludeItemTypes()";
537         if (LOG.isDebugEnabled()) {
538             LOG.debug(methodName + " started");
539         }
540         List<SourceAccountingLine> returnList = generateAccountSummary(items, includedItemTypeCodes, ITEM_TYPES_INCLUDED_VALUE, ZERO_TOTALS_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false);
541         if (LOG.isDebugEnabled()) {
542             LOG.debug(methodName + " ended");
543         }
544         return returnList;
545     }
546 
547     /**
548      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateSummaryExcludeItemTypesAndNoZeroTotals(java.util.List,
549      *      java.util.Set)
550      */
551     @Override
552     public List<SourceAccountingLine> generateSummaryExcludeItemTypesAndNoZeroTotals(List<PurApItem> items, Set excludedItemTypeCodes) {
553         String methodName = "generateSummaryIncludeItemTypesAndNoZeroTotals()";
554         if (LOG.isDebugEnabled()) {
555             LOG.debug(methodName + " started");
556         }
557         List<SourceAccountingLine> returnList = generateAccountSummary(items, excludedItemTypeCodes, ITEM_TYPES_EXCLUDED_VALUE, ZERO_TOTALS_NOT_RETURNED_VALUE, ALTERNATE_AMOUNT_NOT_USED, USE_TAX_INCLUDED, false);
558         if (LOG.isDebugEnabled()) {
559             LOG.debug(methodName + " ended");
560         }
561         return returnList;
562     }
563 
564     /**
565      * Generates an account summary, that is it creates a list of source accounts by rounding up the purap accounts off of the purap
566      * items.
567      *
568      * @param items                    the items to determ
569      * @param itemTypeCodes            the item types to determine whether to look at an item in combination with itemTypeCodesAreIncluded
570      * @param itemTypeCodesAreIncluded value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables
571      * @param useZeroTotals            whether to include items with a zero dollar total
572      * @param useAlternateAmount       an alternate amount used in certain cases for GL entry
573      * @return a list of source accounts
574      */
575     protected List<SourceAccountingLine> generateAccountSummary(List<PurApItem> items, Set<String> itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals, Boolean useAlternateAmount, Boolean useTaxIncluded, Boolean taxableOnly) {
576         List<PurApItem> itemsToProcess = getProcessablePurapItems(items, itemTypeCodes, itemTypeCodesAreIncluded, useZeroTotals);
577         Map<PurApAccountingLine, KualiDecimal> accountMap = new HashMap<PurApAccountingLine, KualiDecimal>();
578 
579         for (PurApItem currentItem : itemsToProcess) {
580             if (PurApItemUtils.checkItemActive(currentItem)) {
581                 List<PurApAccountingLine> sourceAccountingLines = currentItem.getSourceAccountingLines();
582 
583                 // skip if item is not taxable and taxable only flag has been set
584                 if (taxableOnly) {
585                     PurchasingAccountsPayableDocument document = currentItem.getPurapDocument();
586                     if (!purapService.isTaxableForSummary(document.isUseTaxIndicator(), purapService.getDeliveryState(document), currentItem)) {
587                         continue;
588                     }
589                 }
590 
591                 if (!useTaxIncluded) {
592                     // if no use tax set the source accounting lines to a clone so we can update
593                     // them to be based on the non tax amount
594                     if(currentItem instanceof OleInvoiceItem) {
595                         OleInvoiceItem invoiceItem = (OleInvoiceItem) currentItem;
596                     if(invoiceItem.getItemType().isQuantityBasedGeneralLedgerIndicator() &&  invoiceItem.getRelatedViews() != null) {
597                        // invoiceItem.setRelatedViews(null);
598                         PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(invoiceItem);
599                         sourceAccountingLines = cloneItem.getSourceAccountingLines();
600                         updateAccountAmountsWithTotal(sourceAccountingLines, invoiceItem.getTotalRemitAmount());
601                     }
602                     else {
603                         PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(currentItem);
604                         sourceAccountingLines = cloneItem.getSourceAccountingLines();
605                         updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount());
606                     }
607                     }
608                     else {
609                     PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(currentItem);
610                     sourceAccountingLines = cloneItem.getSourceAccountingLines();
611                     updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount());
612                     }
613                 }
614 
615                 for (PurApAccountingLine account : sourceAccountingLines) {
616 
617                     // skip account if not taxable and taxable only flag is set
618                     if (taxableOnly) {
619                         PurchasingAccountsPayableDocument document = currentItem.getPurapDocument();
620                         // check if account is not taxable, if not skip this account
621                         if (!purapService.isAccountingLineTaxable(account, purapService.isDeliveryStateTaxable(purapService.getDeliveryState(document)))) {
622                             continue;
623                         }
624                     }
625 
626                     KualiDecimal total = KualiDecimal.ZERO;
627                     if (account instanceof InvoiceAccount) {
628                         if (((InvoiceAccount) account).getInvoiceItem() != null) {
629                             if (((OleInvoiceItem)((InvoiceAccount) account).getInvoiceItem()).isDebitItem()) {
630                                 if(account.getOrganizationReferenceId()!=null && account.getOrganizationReferenceId().isEmpty()){
631                                     account.setOrganizationReferenceId(null);
632                                 }
633                                 if (accountMap.containsKey(account)) {
634                                     total = accountMap.get(account);
635                                 }
636 
637                                 if (useAlternateAmount) {
638                                     total = total.add(account.getAlternateAmountForGLEntryCreation());
639                                 } else {
640                                     if (ObjectUtils.isNotNull(account.getAmount())) {
641                                         total = total.add(account.getAmount());
642                                     }
643                                 }
644                             }
645                             else {
646                                 if (accountMap.containsKey(account)) {
647                                     total = accountMap.get(account);
648                                 }
649 
650                                 if (useAlternateAmount) {
651                                     total = total.subtract(account.getAlternateAmountForGLEntryCreation());
652                                 } else {
653                                     if (ObjectUtils.isNotNull(account.getAmount())) {
654                                         total = total.subtract(account.getAmount());
655                                     }
656                                 }
657                             }
658                         }
659 
660                     }
661                     else {
662                         // getting the total to set on the account
663                         if (accountMap.containsKey(account)) {
664                             total = accountMap.get(account);
665                         }
666 
667                         if (useAlternateAmount) {
668                             total = total.add(account.getAlternateAmountForGLEntryCreation());
669                         } else {
670                             if (ObjectUtils.isNotNull(account.getAmount())) {
671                                 total = total.add(account.getAmount());
672                             }
673                         }
674                     }
675                     accountMap.put(account, total);
676                 }
677             }
678         }
679 
680         // convert list of PurApAccountingLine objects to SourceAccountingLineObjects
681         Iterator<PurApAccountingLine> iterator = accountMap.keySet().iterator();
682         List<SourceAccountingLine> sourceAccounts = new ArrayList<SourceAccountingLine>();
683         for (Iterator<PurApAccountingLine> iter = iterator; iter.hasNext(); ) {
684             PurApAccountingLine accountToConvert = iter.next();
685             if (accountToConvert.isEmpty()) {
686                 String errorMessage = "Found an 'empty' account in summary generation " + accountToConvert.toString();
687                 LOG.error("generateAccountSummary() " + errorMessage);
688                 throw new RuntimeException(errorMessage);
689             }
690             KualiDecimal sourceLineTotal = accountMap.get(accountToConvert);
691             SourceAccountingLine sourceLine = accountToConvert.generateSourceAccountingLine();
692             sourceLine.setAmount(sourceLineTotal);
693             sourceAccounts.add(sourceLine);
694         }
695 
696         // sort the sourceAccounts list first by account number, then by object code, ignoring chart code
697         Collections.sort(sourceAccounts, new Comparator<SourceAccountingLine>() {
698             @Override
699             public int compare(SourceAccountingLine sal1, SourceAccountingLine sal2) {
700                 int compare = 0;
701                 if (sal1 != null && sal2 != null) {
702                     if (sal1.getAccountNumber() != null && sal2.getAccountNumber() != null) {
703                         compare = sal1.getAccountNumber().compareTo(sal2.getAccountNumber());
704                         if (compare == 0) {
705                             if (sal1.getFinancialObjectCode() != null && sal2.getFinancialObjectCode() != null) {
706                                 compare = sal1.getFinancialObjectCode().compareTo(sal2.getFinancialObjectCode());
707                             }
708                         }
709                     }
710                 }
711                 return compare;
712             }
713         });
714 
715         return sourceAccounts;
716     }
717 
718     /**
719      * This method takes a list of {@link PurchasingApItem} objects and parses through them to see if each one should be processed
720      * according the the other variables passed in.<br>
721      * <br>
722      * Example 1:<br>
723      * items = "ITEM", "SITM", "FRHT", "SPHD"<br>
724      * itemTypeCodes = "FRHT"<br>
725      * itemTypeCodesAreIncluded = ITEM_TYPES_EXCLUDED_VALUE<br>
726      * return items "ITEM", "SITM", "FRHT", "SPHD"<br>
727      * <br>
728      * <br>
729      * Example 2:<br>
730      * items = "ITEM", "SITM", "FRHT", "SPHD"<br>
731      * itemTypeCodes = "ITEM","FRHT"<br>
732      * itemTypeCodesAreIncluded = ITEM_TYPES_INCLUDED_VALUE<br>
733      * return items "ITEM", "FRHT"<br>
734      *
735      * @param items                    - list of {@link PurchasingApItem} objects that need to be parsed
736      * @param itemTypeCodes            - list of {@link org.kuali.ole.module.purap.businessobject.ItemType} codes used in conjunction with
737      *                                 itemTypeCodesAreIncluded parameter
738      * @param itemTypeCodesAreIncluded - value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables
739      *                                 (see {@link #ITEM_TYPES_INCLUDED_VALUE})
740      * @param useZeroTotals            - value to tell whether to include zero dollar items (see {@link #ZERO_TOTALS_RETURNED_VALUE})
741      * @return a list of {@link PurchasingApItem} objects that should be used for processing by calling method
742      */
743     protected List<PurApItem> getProcessablePurapItems(List<PurApItem> items, Set itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals) {
744         String methodName = "getProcessablePurapItems()";
745         List<PurApItem> newItemList = new ArrayList<PurApItem>();
746         // error out if we have an invalid 'itemTypeCodesAreIncluded' value
747         if ((!(ITEM_TYPES_INCLUDED_VALUE.equals(itemTypeCodesAreIncluded))) && (!(ITEM_TYPES_EXCLUDED_VALUE.equals(itemTypeCodesAreIncluded)))) {
748             throwRuntimeException(methodName, "Invalid parameter found while trying to find processable items for dealing with purchasing/accounts payable accounts");
749         }
750         for (PurApItem currentItem : items) {
751             if ((itemTypeCodes != null) && (!(itemTypeCodes.isEmpty()))) {
752                 // we have at least one entry in our item type code list
753                 boolean foundMatchInList = false;
754                 // check to see if this item type code is in the list
755                 for (Iterator iterator = itemTypeCodes.iterator(); iterator.hasNext(); ) {
756                     String itemTypeCode = (String) iterator.next();
757                     // include this item if it's in the included list
758                     if (itemTypeCode.equals(currentItem.getItemType().getItemTypeCode())) {
759                         foundMatchInList = true;
760                         break;
761                     }
762                 }
763                 // check to see if item type code was found and if the list is describing included or excluded item types
764                 if ((foundMatchInList) && (ITEM_TYPES_EXCLUDED_VALUE.equals(itemTypeCodesAreIncluded))) {
765                     // this item type code is in the list
766                     // this item type code is excluded so we skip it
767                     continue; // skips current item
768                 } else if ((!foundMatchInList) && (ITEM_TYPES_INCLUDED_VALUE.equals(itemTypeCodesAreIncluded))) {
769                     // this item type code is not in the list
770                     // this item type code is not included so we skip it
771                     continue; // skips current item
772                 }
773             } else {
774                 // the item type code list is empty
775                 if (ITEM_TYPES_INCLUDED_VALUE.equals(itemTypeCodesAreIncluded)) {
776                     // the item type code list is empty and the list is supposed to contain the item types to include
777                     throwRuntimeException(methodName, "Invalid parameter and list of items found while trying to find processable items for dealing with purchasing/accounts payable accounts");
778                 }
779             }
780             if ((ZERO_TOTALS_NOT_RETURNED_VALUE.equals(useZeroTotals)) && (ObjectUtils.isNull(currentItem.getExtendedPrice()) || ((KualiDecimal.ZERO.compareTo(currentItem.getExtendedPrice())) == 0))) {
781                 // if we don't return zero dollar items then skip this one
782                 continue;
783             }
784             newItemList.add(currentItem);
785         }
786         return newItemList;
787     }
788 
789     /**
790      * @see org.kuali.ole.module.purap.service.PurapAccountingService#updateAccountAmounts(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument)
791      */
792     @Override
793     public void updateAccountAmounts(PurchasingAccountsPayableDocument document) {
794 
795         PurchasingAccountsPayableDocumentBase purApDocument = (PurchasingAccountsPayableDocumentBase) document;
796         String accountDistributionMethod = purApDocument.getAccountDistributionMethod();
797 
798         KualiRuleService kualiRuleService = SpringContext.getBean(KualiRuleService.class);
799         // olePaymentRequestDocument = (OlePaymentRequestDocument)document;
800         // the percent at fiscal approve
801         // don't update if past the AP review level
802         if ((document instanceof PaymentRequestDocument) && purapService.isFullDocumentEntryCompleted(document)) {
803             // update the percent but don't update the amounts if preq and past full entry
804             convertMoneyToPercent((PaymentRequestDocument) document);
805             return;
806         }
807         if ((document instanceof OleInvoiceDocument) && purapService.isFullDocumentEntryCompleted(document)) {
808             // update the percent but don't update the amounts if preq and past full entry
809             convertMoneyToPercent((OleInvoiceDocument) document);
810             return;
811         }
812         document.fixItemReferences();
813 
814         // OLE-3405 : disabling KFS Proportional/sequential distribution methods
815         //if distribution method is sequential and document is PREQ or VCM then...
816 //          if (((document instanceof PaymentRequestDocument)
817 //                  || (document instanceof VendorCreditMemoDocument))
818 //                  && PurapConstants.AccountDistributionMethodCodes.SEQUENTIAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
819 //              if (document instanceof VendorCreditMemoDocument) {
820 //                  VendorCreditMemoDocument cmDocument = (VendorCreditMemoDocument) document;
821 //                  cmDocument.updateExtendedPriceOnItems();
822 //              }
823 //
824 //              // update the accounts amounts for PREQ and distribution method = sequential
825 //              for (PurApItem item : document.getItems()) {
826 //                  updatePreqItemAccountAmounts(item);
827 //              }
828 //
829 //              return;
830 //          }
831 
832         // OLE-3405 : disabling KFS Proportional/sequential distribution methods
833         //if distribution method is proportional and document is PREQ or VCM then...
834 //          if (((document instanceof PaymentRequestDocument) || (document instanceof VendorCreditMemoDocument)) && PurapConstants.AccountDistributionMethodCodes.PROPORTIONAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
835 //              // update the accounts amounts for PREQ and distribution method = sequential
836 //              if (document instanceof VendorCreditMemoDocument) {
837 //                  VendorCreditMemoDocument cmDocument = (VendorCreditMemoDocument) document;
838 //                  cmDocument.updateExtendedPriceOnItems();
839 //              }
840         for(PurApItem purApItem:document.getItems()){
841             for(PurApAccountingLine oldSourceAccountingLine:purApItem.getSourceAccountingLines()) {
842                 if(oldSourceAccountingLine instanceof OleRequisitionAccount) {
843                     ((OleRequisitionAccount)oldSourceAccountingLine).setExistingAmount(oldSourceAccountingLine.getAmount());
844                 }
845                 else if(oldSourceAccountingLine instanceof OlePurchaseOrderAccount) {
846                     ((OlePurchaseOrderAccount)oldSourceAccountingLine).setExistingAmount(oldSourceAccountingLine.getAmount());
847                 }
848                 else if(oldSourceAccountingLine instanceof PaymentRequestAccount) {
849                     ((PaymentRequestAccount)oldSourceAccountingLine).setExistingAmount(oldSourceAccountingLine.getAmount());
850                 }
851                 else if(oldSourceAccountingLine instanceof InvoiceAccount) {
852                     ((InvoiceAccount)oldSourceAccountingLine).setExistingAmount(oldSourceAccountingLine.getAmount());
853                 }
854             }
855         }
856         for (PurApItem item : document.getItems()) {
857             // OLE-3405 : disabling KFS Proportional/sequential distribution methods
858 //                  boolean rulePassed = true;
859 //                  // check any business rules
860 //                  rulePassed &= kualiRuleService.applyRules(new PurchasingAccountsPayableItemPreCalculateEvent(document, item));
861 //
862 //                  if (rulePassed) {
863 //                      updatePreqProportionalItemAccountAmounts(item);
864             updateItemAccountAmounts(item);
865             if (item instanceof OlePaymentRequestItem) {
866                 updateItemAccountAmountsForAdditionalCharge(item, (OlePaymentRequestDocument) document);
867             } else if (item instanceof OleCreditMemoItem) {
868                 updateItemAccountAmountsForAdditionalCharge(item, (OleVendorCreditMemoDocument) document);
869             } else if (item instanceof OleInvoiceItem) {
870                 updateItemAccountAmountsForAdditionalCharge(item, (OleInvoiceDocument) document);
871             } else {
872                 updateItemAccountAmounts(item);
873 //                          updatePreqProportionalItemAccountAmounts(item);
874             }
875 //                  }
876         }
877 
878         // OLE-3405 : disabling KFS Proportional/sequential distribution methods
879 //              return;
880 //          }
881 
882         // OLE-3405 : disabling KFS Proportional/sequential distribution methods
883         //No recalculate if the account distribution method code is equal to "S" sequential ON REQ or POs..
884 //          if (PurapConstants.AccountDistributionMethodCodes.SEQUENTIAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
885 //              for (PurApItem item : document.getItems()) {
886 //                  boolean rulePassed = true;
887 //                  // check any business rules
888 //                  rulePassed &= kualiRuleService.applyRules(new PurchasingAccountsPayableItemPreCalculateEvent(document, item));
889 //
890 //                  return;
891 //              }
892 //          }
893 //
894 //          //do recalculate only if the account distribution method code is not equal to "S" sequential method.
895 //          if (!PurapConstants.AccountDistributionMethodCodes.SEQUENTIAL_CODE.equalsIgnoreCase(accountDistributionMethod)) {
896 //              for (PurApItem item : document.getItems()) {
897 //                  boolean rulePassed = true;
898 //                  // check any business rules
899 //                  rulePassed &= kualiRuleService.applyRules(new PurchasingAccountsPayableItemPreCalculateEvent(document, item));
900 //
901 //                  if (rulePassed) {
902 //              updateItemAccountAmounts(item);
903 //          }
904 //      }
905 //          }
906     }
907 
908     public void updateItemAccountAmountsForAdditionalCharge(PurApItem item, OlePaymentRequestDocument document) {
909         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
910         OlePaymentRequestItem olePaymentRequestItem = (OlePaymentRequestItem) item;
911         KualiDecimal prorateSurcharge = new KualiDecimal();
912         KualiDecimal qty = KualiDecimal.ZERO;
913         if (olePaymentRequestItem.getItemSurcharge() == null) {
914             prorateSurcharge = KualiDecimal.ZERO;
915         } else {
916             qty = olePaymentRequestItem.getItemQuantity();
917             prorateSurcharge = new KualiDecimal(olePaymentRequestItem.getItemSurcharge());
918         }
919         KualiDecimal totalAmount = item.getTotalAmount();
920         totalAmount = totalAmount.subtract(prorateSurcharge.multiply(qty));
921         updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
922     }
923 
924     public void updateItemAccountAmountsForAdditionalCharge(PurApItem item, OleInvoiceDocument document) {
925         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
926         OleInvoiceItem oleInvoiceItem = (OleInvoiceItem) item;
927         KualiDecimal prorateSurcharge = new KualiDecimal();
928         KualiDecimal qty = KualiDecimal.ZERO;
929         if (oleInvoiceItem.getItemSurcharge() == null) {
930             prorateSurcharge = KualiDecimal.ZERO;
931         } else {
932             qty = oleInvoiceItem.getItemQuantity();
933             prorateSurcharge = new KualiDecimal(oleInvoiceItem.getItemSurcharge());
934         }
935         KualiDecimal totalAmount = item.getTotalAmount();
936         totalAmount = totalAmount.subtract(prorateSurcharge.multiply(qty));
937         updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
938     }
939 
940     private void updateItemAccountAmountsForAdditionalCharge(PurApItem item, OleVendorCreditMemoDocument document) {
941 
942         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
943         OleCreditMemoItem oleCreditMemoItem = (OleCreditMemoItem) item;
944         KualiDecimal prorateSurcharge = new KualiDecimal();
945         KualiDecimal qty = KualiDecimal.ZERO;
946         if (oleCreditMemoItem.getItemSurcharge() == null) {
947             prorateSurcharge = KualiDecimal.ZERO;
948         } else {
949             qty = oleCreditMemoItem.getItemQuantity();
950             prorateSurcharge = new KualiDecimal(oleCreditMemoItem.getItemSurcharge());
951         }
952         KualiDecimal totalAmount = item.getTotalAmount();
953         totalAmount = totalAmount.subtract(prorateSurcharge.multiply(qty));
954         updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
955 
956     }
957 
958     /**
959      * @see org.kuali.ole.module.purap.service.PurapAccountingService#updateItemAccountAmounts(org.kuali.ole.module.purap.businessobject.PurApItem)
960      */
961     @Override
962     public void updateItemAccountAmounts(PurApItem item) {
963         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
964         KualiDecimal totalAmount = item.getTotalAmount();
965         if (item.getItemType().isAdditionalChargeIndicator()) {
966             updateAccountAmountsWithTotalForAdditionalCharge(sourceAccountingLines, totalAmount);
967         } else {
968             updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
969         }
970     }
971 
972     /*public void updateItemAccountAmountsForAdditionalCharge(PurApItem item) {
973         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
974         KualiDecimal totalAmount = item.getTotalAmount();
975             updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
976     }*/
977 
978     /**
979      * calculates values for a list of accounting lines based on an amount
980      *
981      * @param sourceAccountingLines
982      * @param totalAmount
983      */
984 
985     public <T extends PurApAccountingLine> void updateAccountAmountsWithTotalForAdditionalCharge(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
986         if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
987 
988             KualiDecimal accountTotal = KualiDecimal.ZERO;
989             T lastAccount = null;
990 
991 
992             for (T account : sourceAccountingLines) {
993                 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
994                     BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100));
995                     account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
996                 } else {
997                     account.setAmount(KualiDecimal.ZERO);
998                 }
999                 accountTotal = accountTotal.add(account.getAmount());
1000                 lastAccount = account;
1001             }
1002 
1003             // put excess on last account
1004             if (lastAccount != null) {
1005                 KualiDecimal difference = totalAmount.subtract(accountTotal);
1006                 lastAccount.setAmount(lastAccount.getAmount().add(difference));
1007             }
1008         } else {
1009             // zero out if extended price is zero
1010             for (T account : sourceAccountingLines) {
1011                 account.setAmount(KualiDecimal.ZERO);
1012             }
1013         }
1014     }
1015 
1016     public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal2(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
1017         if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
1018 
1019             KualiDecimal accountTotal = KualiDecimal.ZERO;
1020             T lastAccount = null;
1021 
1022 
1023             for (T account : sourceAccountingLines) {
1024                 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
1025                     //    OlePaymentRequestItem item = new OlePaymentRequestItem();
1026                     BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100));
1027                     account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
1028                 } else {
1029                     account.setAmount(KualiDecimal.ZERO);
1030                 }
1031                 accountTotal = accountTotal.add(account.getAmount());
1032                 lastAccount = account;
1033             }
1034 
1035             // put excess on last account
1036             if (lastAccount != null) {
1037                 KualiDecimal difference = totalAmount.subtract(accountTotal);
1038                 lastAccount.setAmount(lastAccount.getAmount().add(difference));
1039             }
1040         } else {
1041             // zero out if extended price is zero
1042             for (T account : sourceAccountingLines) {
1043                 account.setAmount(KualiDecimal.ZERO);
1044             }
1045         }
1046     }
1047 
1048     /**
1049      * calculates values for a list of accounting lines based on an amount
1050      *
1051      * @param sourceAccountingLines
1052      * @param totalAmount
1053      */
1054     @Override
1055     public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
1056         updateAccountAmountsWithTotal(sourceAccountingLines, totalAmount, new KualiDecimal(0));
1057     }
1058 
1059     /**
1060      * calculates values for a list of accounting lines based on an amount taking discount into account
1061      *
1062      * @param sourceAccountingLines
1063      * @param totalAmount
1064      * @param discountAmount
1065      */
1066     @Override
1067     public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal(List<T> sourceAccountingLines,
1068                                                                               KualiDecimal totalAmount, KualiDecimal discountAmount) {
1069 
1070         // if we have a discount, then we need to base the amounts on the discount, but the percent on the total
1071         boolean noDiscount = true;
1072         if ((discountAmount != null) && KualiDecimal.ZERO.compareTo(discountAmount) != 0) {
1073             noDiscount = false;
1074         }
1075 
1076         if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
1077 
1078             KualiDecimal accountTotal = KualiDecimal.ZERO;
1079             BigDecimal accountTotalPercent = BigDecimal.ZERO;
1080             T lastAccount = null;
1081 
1082             for (T account : sourceAccountingLines) {
1083                 if (ObjectUtils.isNotNull(account.getAccountLinePercent()) || ObjectUtils.isNotNull(account.getAmount())) {
1084                     if (ObjectUtils.isNotNull(account.getAmount()) && account.getAmount().isGreaterThan(KualiDecimal.ZERO)) {
1085                         KualiDecimal amt = account.getAmount();
1086                         KualiDecimal calculatedPercent = new KualiDecimal(amt.multiply(new KualiDecimal(100)).divide(totalAmount).toString());
1087                         account.setAccountLinePercent(calculatedPercent.bigDecimalValue().setScale(BIG_DECIMAL_SCALE));
1088                     }
1089 
1090                     if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
1091                         BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100));
1092                         if (noDiscount) {
1093                             if (ObjectUtils.isNull(account.getAmount()) || account.getAmount().isZero()) {
1094                                 account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString()))
1095                                         .setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
1096                             }
1097                         } else {
1098                             account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(discountAmount.toString()))
1099                                     .setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
1100                         }
1101                     }
1102                 }
1103 
1104                 if (ObjectUtils.isNotNull(account.getAmount())) {
1105                     accountTotal = accountTotal.add(account.getAmount());
1106                 }
1107 
1108                 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
1109                     accountTotalPercent = accountTotalPercent.add(account.getAccountLinePercent());
1110                 }
1111 
1112                 lastAccount = account;
1113             }
1114 
1115             // put excess on last account
1116             if (lastAccount != null) {
1117                 KualiDecimal difference = new KualiDecimal(0);
1118                 if (noDiscount) {
1119                     difference = totalAmount.subtract(accountTotal);
1120                 } else {
1121                     difference = discountAmount.subtract(accountTotal);
1122                 }
1123                 if (ObjectUtils.isNotNull(lastAccount.getAmount())) {
1124                     if((difference.abs()).isLessEqual(new KualiDecimal(1).multiply(new KualiDecimal(sourceAccountingLines.size()).divide(new KualiDecimal(2))))) {
1125                         lastAccount.setAmount(lastAccount.getAmount().add(difference));
1126                 }
1127                     else {
1128                         lastAccount.setAmount(lastAccount.getAmount());
1129                     }
1130                     }
1131                 BigDecimal percentDifference = new BigDecimal(100).subtract(accountTotalPercent).setScale(BIG_DECIMAL_SCALE,BigDecimal.ROUND_CEILING);
1132                 if (ObjectUtils.isNotNull(lastAccount.getAccountLinePercent())) {
1133 
1134                     KualiDecimal differencePercent = (((new KualiDecimal(accountTotalPercent)).subtract(new KualiDecimal(100))).abs());
1135                     if((differencePercent.abs()).isLessEqual(new KualiDecimal(1).multiply((new KualiDecimal(sourceAccountingLines.size()).divide(new KualiDecimal(2)))))) {
1136                       lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent().add(percentDifference));
1137                     }
1138                     else {
1139                         lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent());
1140                     }
1141                 }
1142             }
1143         } else {
1144             // zero out if extended price is zero
1145             for (T account : sourceAccountingLines) {
1146                 if (ObjectUtils.isNotNull(account.getAmount())) {
1147                     account.setAmount(KualiDecimal.ZERO);
1148                 }
1149             }
1150         }
1151     }
1152 
1153     /**
1154      * @see org.kuali.ole.module.purap.service.PurapAccountingService#updatePreqProportionalItemAccountAmounts(org.kuali.ole.module.purap.businessobject.PurApItem)
1155      */
1156     @Override
1157     public void updatePreqProportionalItemAccountAmounts(PurApItem item) {
1158         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
1159         KualiDecimal totalAmount = item.getTotalAmount();
1160 
1161         updatePreqProporationalAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
1162     }
1163 
1164     /**
1165      * calculates values for a list of accounting lines based on an amount for proportional method
1166      *
1167      * @param sourceAccountingLines
1168      * @param totalAmount
1169      */
1170     @Override
1171     public <T extends PurApAccountingLine> void updatePreqProporationalAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
1172         if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
1173             KualiDecimal accountTotal = KualiDecimal.ZERO;
1174             BigDecimal accountTotalPercent = BigDecimal.ZERO;
1175             T lastAccount = null;
1176 
1177             for (T account : sourceAccountingLines) {
1178                 if (ObjectUtils.isNotNull(account.getAccountLinePercent()) || ObjectUtils.isNotNull(account.getAmount())) {
1179                     if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
1180                         BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100));
1181                         account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
1182                     }
1183                 }
1184 
1185                 if (ObjectUtils.isNotNull(account.getAmount())) {
1186                     accountTotal = accountTotal.add(account.getAmount());
1187                 }
1188                 if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
1189                     accountTotalPercent = accountTotalPercent.add(account.getAccountLinePercent());
1190                 }
1191 
1192                 lastAccount = account;
1193             }
1194 
1195             // put excess on last account
1196             if (lastAccount != null) {
1197                 KualiDecimal difference = totalAmount.subtract(accountTotal);
1198                 if (ObjectUtils.isNotNull(lastAccount.getAmount())) {
1199                     lastAccount.setAmount(lastAccount.getAmount().add(difference));
1200                 }
1201 
1202                 BigDecimal percentDifference = new BigDecimal(100).subtract(accountTotalPercent).setScale(BIG_DECIMAL_SCALE,BigDecimal.ROUND_CEILING);
1203                 if (ObjectUtils.isNotNull(lastAccount.getAccountLinePercent())) {
1204                     lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent().add(percentDifference));
1205                 }
1206             }
1207         }
1208     }
1209 
1210     /**
1211      * @see org.kuali.ole.module.purap.service.PurapAccountingService#updatePreqItemAccountAmounts(org.kuali.ole.module.purap.businessobject.PurApItem)
1212      */
1213     @Override
1214     public void updatePreqItemAccountAmounts(PurApItem item) {
1215         List<PurApAccountingLine> sourceAccountingLines = item.getSourceAccountingLines();
1216         KualiDecimal totalAmount = item.getTotalAmount();
1217 
1218         updatePreqAccountAmountsWithTotal(sourceAccountingLines, totalAmount);
1219     }
1220 
1221     /**
1222      * calculates values for a list of accounting lines based on an amount.  Preq item's extended
1223      * cost is distributed to the accounting lines.
1224      *
1225      * @param sourceAccountingLines
1226      * @param totalAmount
1227      */
1228     @Override
1229     public <T extends PurApAccountingLine> void updatePreqAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
1230         if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
1231             KualiDecimal accountTotal = KualiDecimal.ZERO;
1232             BigDecimal accountTotalPercent = BigDecimal.ZERO;
1233             T lastAccount = null;
1234 
1235             for (T account : sourceAccountingLines) {
1236                 //look at lines where amount is non-zero..
1237                 if (account.getAmount().isGreaterThan(KualiDecimal.ZERO)) {
1238                     if (totalAmount.isZero()) {
1239                         account.setAmount(KualiDecimal.ZERO);
1240                     } else {
1241                         if (account.getAmount().isGreaterThan(totalAmount)) {
1242                             account.setAmount(totalAmount);
1243                         }
1244                     }
1245                 }
1246 
1247                 totalAmount = totalAmount.subtract(account.getAmount());
1248             }
1249 
1250             if (totalAmount.isGreaterThan(KualiDecimal.ZERO)) {
1251                 for (T account : sourceAccountingLines) {
1252                     if (account.getAmount().isZero() || account.getAccountLinePercent().compareTo(BigDecimal.ZERO) == 1) {
1253                         KualiDecimal priorAmount = account.getAmount();
1254                         account.setAmount(account.getAmount().add(new KualiDecimal(account.getAccountLinePercent()).multiply(totalAmount).divide(new KualiDecimal(100))));
1255                         accountTotal = accountTotal.add(account.getAmount().subtract(priorAmount));
1256                         lastAccount = account;
1257                     }
1258                 }
1259             }
1260 
1261             accountTotal = totalAmount.subtract(accountTotal);
1262 
1263             if (accountTotal.isGreaterThan(KualiDecimal.ZERO) && ObjectUtils.isNotNull(lastAccount)) {
1264                 //add the difference to the last overage account....
1265                 lastAccount.setAmount(lastAccount.getAmount().add(accountTotal));
1266             }
1267         }
1268     }
1269 
1270     public List<PurApAccountingLine> generatePercentSummary(PurchasingAccountsPayableDocument purapDoc) {
1271         List<PurApAccountingLine> accounts = new ArrayList<PurApAccountingLine>();
1272         for (PurApItem currentItem : purapDoc.getItems()) {
1273             if (PurApItemUtils.checkItemActive(currentItem)) {
1274                 for (PurApAccountingLine account : currentItem.getSourceAccountingLines()) {
1275                     boolean thisAccountAlreadyInSet = false;
1276                     for (Object element : accounts) {
1277                         PurApAccountingLine alreadyAddedAccount = (PurApAccountingLine) element;
1278 
1279 
1280                         if (alreadyAddedAccount.accountStringsAreEqual(account)) {
1281                             BigDecimal alreadyAddedAccountLinePercent = BigDecimal.ZERO;
1282                             if (ObjectUtils.isNotNull(alreadyAddedAccount.getAccountLinePercent())) {
1283                                 alreadyAddedAccountLinePercent = alreadyAddedAccount.getAccountLinePercent();
1284                             }
1285                             BigDecimal accountLinePercent = BigDecimal.ZERO;
1286                             if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
1287                                 accountLinePercent = account.getAccountLinePercent();
1288                             }
1289 
1290                             alreadyAddedAccount.setAccountLinePercent(alreadyAddedAccountLinePercent.add(accountLinePercent));
1291 
1292                             thisAccountAlreadyInSet = true;
1293                             break;
1294                         }
1295                     }
1296                     if (!thisAccountAlreadyInSet) {
1297                         PurApAccountingLine accountToAdd = (PurApAccountingLine) ObjectUtils.deepCopy(account);
1298                         accounts.add(accountToAdd);
1299                     }
1300                 }
1301             }
1302         }
1303         return accounts;
1304     }
1305 
1306     /**
1307      * @see org.kuali.ole.module.purap.service.PurapAccountingService#convertMoneyToPercent(org.kuali.ole.module.purap.document.PaymentRequestDocument)
1308      */
1309     @Override
1310     public void convertMoneyToPercent(PaymentRequestDocument pr) {
1311         LOG.debug("convertMoneyToPercent() started");
1312 
1313         int itemNbr = 0;
1314 
1315         for (Iterator<PaymentRequestItem> iter = pr.getItems().iterator(); iter.hasNext(); ) {
1316             PaymentRequestItem item = iter.next();
1317 
1318             itemNbr++;
1319             String identifier = item.getItemIdentifierString();
1320 
1321             if (item.getTotalAmount() != null && item.getTotalAmount().isNonZero()) {
1322                 int numOfAccounts = item.getSourceAccountingLines().size();
1323                 BigDecimal percentTotal = BigDecimal.ZERO;
1324                 KualiDecimal accountTotal = KualiDecimal.ZERO;
1325                 int accountIdentifier = 0;
1326 
1327                 KualiDecimal addChargeItem = KualiDecimal.ZERO;
1328                 KualiDecimal lineItemPreTaxTotal = KualiDecimal.ZERO;
1329                /* KualiDecimal prorateSurcharge = KualiDecimal.ZERO;
1330                 if (item.getItemType().isQuantityBasedGeneralLedgerIndicator() && item.getExtendedPrice() != null && item.getExtendedPrice().compareTo(KualiDecimal.ZERO) != 0) {
1331                     if (((OlePaymentRequestItem) item).getItemSurcharge() != null) {
1332                         prorateSurcharge = new KualiDecimal(((OlePaymentRequestItem) item).getItemSurcharge()).multiply(item.getItemQuantity());
1333                     }
1334                 }*/
1335                 PurApAccountingLine lastAccount = null;
1336                 BigDecimal accountTotalPercent = BigDecimal.ZERO;
1337 
1338                 for (PurApAccountingLine purApAccountingLine : item.getSourceAccountingLines()) {
1339                     accountIdentifier++;
1340                     PaymentRequestAccount account = (PaymentRequestAccount) purApAccountingLine;
1341 
1342                     // account.getAmount returns the wrong value for trade in source accounting lines...
1343                     KualiDecimal accountAmount = KualiDecimal.ZERO;
1344                     if (ObjectUtils.isNotNull(account.getAmount())) {
1345                         accountAmount = account.getAmount();
1346                     }
1347 
1348                     BigDecimal tmpPercent = BigDecimal.ZERO;
1349                     KualiDecimal extendedPrice = item.getTotalAmount();
1350                     tmpPercent = accountAmount.bigDecimalValue().divide(extendedPrice.bigDecimalValue(), PurapConstants.CREDITMEMO_PRORATION_SCALE.intValue(), KualiDecimal.ROUND_BEHAVIOR);
1351 
1352                     if (accountIdentifier == numOfAccounts) {
1353                         // if on last account, calculate the percent by subtracting current percent total from 1
1354                         tmpPercent = BigDecimal.ONE.subtract(percentTotal);
1355                     }
1356 
1357                     // test that the above amount is correct, if so just check that the total of all these matches the item total
1358                     BigDecimal calcAmountBd = tmpPercent.multiply(extendedPrice.bigDecimalValue());
1359                     calcAmountBd = calcAmountBd.setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR);
1360                     KualiDecimal calcAmount = new KualiDecimal(calcAmountBd);
1361                     //calcAmount = calcAmount.subtract(prorateSurcharge);
1362                     if (calcAmount.compareTo(accountAmount) != 0) {
1363                         // rounding error
1364                         if (LOG.isDebugEnabled()) {
1365                             LOG.debug("convertMoneyToPercent() Rounding error on " + account);
1366                         }
1367                         String param1 = identifier + "." + accountIdentifier;
1368                         String param2 = calcAmount.bigDecimalValue().subtract(accountAmount.bigDecimalValue()).toString();
1369                         GlobalVariables.getMessageMap().putError(item.getItemIdentifierString(), PurapKeyConstants.ERROR_ITEM_ACCOUNTING_ROUNDING, param1, param2);
1370                         account.setAmount(calcAmount);
1371                     }
1372 
1373                     // update percent
1374                     if (LOG.isDebugEnabled()) {
1375                         LOG.debug("convertMoneyToPercent() updating percent to " + tmpPercent);
1376                     }
1377                     account.setAccountLinePercent(tmpPercent.multiply(new BigDecimal(100)));
1378                     accountTotalPercent = accountTotalPercent.add(account.getAccountLinePercent());
1379                     lastAccount = account;
1380                     // check total based on adjusted amount
1381                     accountTotal = accountTotal.add(calcAmount);
1382                     percentTotal = percentTotal.add(tmpPercent);
1383                 }
1384                 BigDecimal percentDifference = new BigDecimal(100).subtract(accountTotalPercent).setScale(BIG_DECIMAL_SCALE,BigDecimal.ROUND_CEILING);
1385                 if (ObjectUtils.isNotNull(lastAccount.getAccountLinePercent())) {
1386                     KualiDecimal differencePercent = (((new KualiDecimal(accountTotalPercent)).subtract(new KualiDecimal(100))).abs());
1387                     if((differencePercent.abs()).isLessEqual(new KualiDecimal(1).multiply((new KualiDecimal(item.getSourceAccountingLines().size()).divide(new KualiDecimal(2)))))) {
1388                         lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent().add(percentDifference));
1389                     }
1390                     else {
1391                         lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent());
1392                     }
1393                 }
1394             }
1395         }
1396     }
1397 
1398 
1399     @Override
1400     public void convertMoneyToPercent(InvoiceDocument inv) {
1401         LOG.debug("convertMoneyToPercent() started");
1402 
1403         int itemNbr = 0;
1404 
1405         for (Iterator<InvoiceItem> iter = inv.getItems().iterator(); iter.hasNext(); ) {
1406             InvoiceItem item = iter.next();
1407 
1408             itemNbr++;
1409             String identifier = item.getItemIdentifierString();
1410 
1411             if (item.getTotalAmount() != null && item.getTotalAmount().isNonZero()) {
1412                 int numOfAccounts = item.getSourceAccountingLines().size();
1413                 BigDecimal percentTotal = BigDecimal.ZERO;
1414                 KualiDecimal accountTotal = KualiDecimal.ZERO;
1415                 int accountIdentifier = 0;
1416 
1417                 KualiDecimal addChargeItem = KualiDecimal.ZERO;
1418                 KualiDecimal lineItemPreTaxTotal = KualiDecimal.ZERO;
1419                 KualiDecimal prorateSurcharge = KualiDecimal.ZERO;
1420                 if (item.getItemType().isQuantityBasedGeneralLedgerIndicator() && item.getExtendedPrice() != null && item.getExtendedPrice().compareTo(KualiDecimal.ZERO) != 0) {
1421                     if (((OleInvoiceItem) item).getItemSurcharge() != null) {
1422                         prorateSurcharge = new KualiDecimal(((OleInvoiceItem) item).getItemSurcharge()).multiply(item.getItemQuantity());
1423                     }
1424                 }
1425                 PurApAccountingLine lastAccount = null;
1426                 BigDecimal accountTotalPercent = BigDecimal.ZERO;
1427                 for (PurApAccountingLine purApAccountingLine : item.getSourceAccountingLines()) {
1428                     accountIdentifier++;
1429                     InvoiceAccount account = (InvoiceAccount) purApAccountingLine;
1430 
1431                     // account.getAmount returns the wrong value for trade in source accounting lines...
1432                     KualiDecimal accountAmount = KualiDecimal.ZERO;
1433                     if (ObjectUtils.isNotNull(account.getAmount())) {
1434                         accountAmount = account.getAmount();
1435                     }
1436 
1437                     BigDecimal tmpPercent = BigDecimal.ZERO;
1438                     KualiDecimal extendedPrice = item.getTotalAmount();
1439                     tmpPercent = accountAmount.bigDecimalValue().divide(extendedPrice.bigDecimalValue(), PurapConstants.CREDITMEMO_PRORATION_SCALE.intValue(), KualiDecimal.ROUND_BEHAVIOR);
1440 
1441                     if (accountIdentifier == numOfAccounts) {
1442                         // if on last account, calculate the percent by subtracting current percent total from 1
1443                         tmpPercent = BigDecimal.ONE.subtract(percentTotal);
1444                     }
1445 
1446                     // test that the above amount is correct, if so just check that the total of all these matches the item total
1447                     BigDecimal calcAmountBd = tmpPercent.multiply(extendedPrice.bigDecimalValue());
1448                     calcAmountBd = calcAmountBd.setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR);
1449                     KualiDecimal calcAmount = new KualiDecimal(calcAmountBd);
1450                     calcAmount = calcAmount.subtract(prorateSurcharge);
1451                     if (calcAmount.compareTo(accountAmount) != 0) {
1452                         // rounding error
1453                         if (LOG.isDebugEnabled()) {
1454                             LOG.debug("convertMoneyToPercent() Rounding error on " + account);
1455                         }
1456                         String param1 = identifier + "." + accountIdentifier;
1457                         String param2 = calcAmount.bigDecimalValue().subtract(accountAmount.bigDecimalValue()).toString();
1458                         GlobalVariables.getMessageMap().putError(item.getItemIdentifierString(), PurapKeyConstants.ERROR_ITEM_ACCOUNTING_ROUNDING, param1, param2);
1459                         account.setAmount(calcAmount);
1460                     }
1461 
1462                     // update percent
1463                     if (LOG.isDebugEnabled()) {
1464                         LOG.debug("convertMoneyToPercent() updating percent to " + tmpPercent);
1465                     }
1466                     account.setAccountLinePercent(tmpPercent.multiply(new BigDecimal(100)));
1467                     accountTotalPercent = accountTotalPercent.add(account.getAccountLinePercent());
1468                     lastAccount = account;
1469 
1470                     // check total based on adjusted amount
1471                     accountTotal = accountTotal.add(calcAmount);
1472                     percentTotal = percentTotal.add(tmpPercent);
1473                 }
1474                 BigDecimal percentDifference = new BigDecimal(100).subtract(accountTotalPercent).setScale(BIG_DECIMAL_SCALE,BigDecimal.ROUND_CEILING);
1475                 if (ObjectUtils.isNotNull(lastAccount) && ObjectUtils.isNotNull(lastAccount.getAccountLinePercent())) {
1476                     KualiDecimal differencePercent = (((new KualiDecimal(accountTotalPercent)).subtract(new KualiDecimal(100))).abs());
1477                     if((differencePercent.abs()).isLessEqual(new KualiDecimal(1).multiply((new KualiDecimal(item.getSourceAccountingLines().size()).divide(new KualiDecimal(2)))))) {
1478                         lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent().add(percentDifference));
1479                     }
1480                     else {
1481                         lastAccount.setAccountLinePercent(lastAccount.getAccountLinePercent());
1482                     }
1483                 }
1484             }
1485         }
1486     }
1487 
1488     /**
1489      * @see org.kuali.ole.module.purap.service.PurapAccountingService#deleteSummaryAccounts(java.lang.Integer, java.lang.String)
1490      */
1491     @Override
1492     public void deleteSummaryAccounts(Integer purapDocumentIdentifier, String docType) {
1493         if (PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT.equals(docType)) {
1494             purApAccountingDao.deleteSummaryAccountsbyPaymentRequestIdentifier(purapDocumentIdentifier);
1495         } else if (PurapDocTypeCodes.CREDIT_MEMO_DOCUMENT.equals(docType)) {
1496             purApAccountingDao.deleteSummaryAccountsbyCreditMemoIdentifier(purapDocumentIdentifier);
1497         } else if (PurapDocTypeCodes.INVOICE_DOCUMENT.equals(docType)) {
1498             purApAccountingDao.deleteSummaryAccountsbyInvoiceIdentifier(purapDocumentIdentifier);
1499         }
1500     }
1501 
1502     @Override
1503     public List getAccountsPayableSummaryAccounts(Integer purapDocumentIdentifier, String docType) {
1504         if (PurapDocTypeCodes.PAYMENT_REQUEST_DOCUMENT.equals(docType)) {
1505             return getSummaryAccountsbyPaymentRequestIdentifier(purapDocumentIdentifier);
1506         } else if (PurapDocTypeCodes.CREDIT_MEMO_DOCUMENT.equals(docType)) {
1507             getSummaryAccountsbyCreditMemoIdentifier(purapDocumentIdentifier);
1508         } else if (PurapDocTypeCodes.INVOICE_DOCUMENT.equals(docType)) {
1509             getSummaryAccountsbyInvoiceIdentifier(purapDocumentIdentifier);
1510         }
1511         return null;
1512     }
1513 
1514     @Override
1515     public List<PurApAccountingLine> getAccountsFromItem(PurApItem item) {
1516         return purApAccountingDao.getAccountingLinesForItem(item);
1517     }
1518 
1519     @Override
1520     public List<SourceAccountingLine> generateSourceAccountsForVendorRemit(PurchasingAccountsPayableDocument document) {
1521         // correct initial amounts or percents
1522         //updateAccountAmounts(document);
1523         List<SourceAccountingLine> vendorSummaryAccounts = new ArrayList<SourceAccountingLine>();
1524 
1525         // update accounts here with amounts to send to vendor
1526         vendorSummaryAccounts = generateSummaryWithNoZeroTotalsNoUseTax(document.getItems());
1527 
1528         return vendorSummaryAccounts;
1529     }
1530 
1531     /**
1532      * gets sum total of accounts
1533      *
1534      * @param accounts
1535      * @return
1536      */
1537 
1538     protected KualiDecimal calculateSumTotal(List<SourceAccountingLine> accounts) {
1539         KualiDecimal total = KualiDecimal.ZERO;
1540         for (SourceAccountingLine accountingLine : accounts) {
1541             KualiDecimal amt = KualiDecimal.ZERO;
1542             if (ObjectUtils.isNotNull(accountingLine.getAmount())) {
1543                 amt = accountingLine.getAmount();
1544             }
1545             total = total.add(amt);
1546         }
1547         return total;
1548     }
1549 
1550     /**
1551      * Replaces amount field with prorated tax amount in list
1552      *
1553      * @param accounts       list of accounts
1554      * @param useTax         tax to be allocated to these accounts
1555      * @param newSourceLines rewrites the source account lines
1556      */
1557 
1558     protected void convertAmtToTax(List<PurApAccountingLine> accounts, KualiDecimal useTax, List<SourceAccountingLine> newSourceLines) {
1559         final BigDecimal HUNDRED = new BigDecimal(100);
1560         PurApAccountingLine purApAccountingLine;
1561         BigDecimal proratedAmtBD;
1562         KualiDecimal proratedAmt;
1563         // convert back to source
1564         KualiDecimal total = KualiDecimal.ZERO;
1565         int last = accounts.size() - 1;
1566         for (int i = 0; i < last; i++) {
1567             purApAccountingLine = accounts.get(i);
1568             BigDecimal linePercent = BigDecimal.ZERO;
1569             if (ObjectUtils.isNotNull(purApAccountingLine.getAccountLinePercent())) {
1570                 linePercent = purApAccountingLine.getAccountLinePercent();
1571             }
1572 
1573             proratedAmtBD = useTax.bigDecimalValue().multiply(linePercent);
1574             // last object takes the rest of the amount
1575             // proratedAmt = (accounts.indexOf(purApAccountingLine) == last) ? useTax.subtract(total) : proratedAmt.divide(HUNDRED);
1576             proratedAmtBD = proratedAmtBD.divide(HUNDRED);
1577             proratedAmt = new KualiDecimal(proratedAmtBD);
1578             SourceAccountingLine acctLine = purApAccountingLine.generateSourceAccountingLine();
1579             acctLine.setAmount(proratedAmt);
1580             newSourceLines.add(acctLine);
1581             total = total.add(proratedAmt);
1582         }
1583         // update last object with remaining balance
1584         proratedAmt = useTax.subtract(total);
1585         purApAccountingLine = accounts.get(last);
1586         SourceAccountingLine acctLine = purApAccountingLine.generateSourceAccountingLine();
1587         acctLine.setAmount(proratedAmt);
1588         newSourceLines.add(acctLine);
1589     }
1590 
1591     /**
1592      * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateUseTaxAccount(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument)
1593      */
1594     @Override
1595     public List<UseTaxContainer> generateUseTaxAccount(PurchasingAccountsPayableDocument document) {
1596         List<UseTaxContainer> useTaxAccounts = new ArrayList<UseTaxContainer>();
1597 
1598         HashMap<PurApItemUseTax, UseTaxContainer> useTaxItemMap = new HashMap<PurApItemUseTax, UseTaxContainer>();
1599         Class accountingLineClass = null;
1600         if (!document.isUseTaxIndicator()) {
1601             // not useTax, return
1602             return useTaxAccounts;
1603         }
1604         for (PurApItem purApItem : document.getItems()) {
1605             if (!purApItem.getUseTaxItems().isEmpty()) {
1606                 if (accountingLineClass == null) {
1607                     accountingLineClass = purApItem.getAccountingLineClass();
1608                 }
1609                 UseTaxContainer useTaxContainer = new UseTaxContainer();
1610                 for (PurApItemUseTax itemUseTax : purApItem.getUseTaxItems()) {
1611                     if (useTaxItemMap.containsKey(itemUseTax)) {
1612                         useTaxContainer = useTaxItemMap.get(itemUseTax);
1613                         PurApItemUseTax exisitingItemUseTax = useTaxContainer.getUseTax();
1614                         // if already in set we need to add on the old amount
1615                         KualiDecimal tax = exisitingItemUseTax.getTaxAmount();
1616                         tax = tax.add(itemUseTax.getTaxAmount());
1617                         exisitingItemUseTax.setTaxAmount(tax);
1618 
1619                         List<PurApItem> items = useTaxContainer.getItems();
1620                         items.add(purApItem);
1621                         useTaxContainer.setItems(items);
1622 
1623                     } else {
1624                         useTaxContainer = new UseTaxContainer(itemUseTax, purApItem);
1625                         useTaxItemMap.put(itemUseTax, useTaxContainer);
1626                         useTaxAccounts.add(useTaxContainer);
1627                     }
1628                 }
1629             }
1630         }
1631         // iterate over useTaxAccounts and set summary accounts using proration
1632         for (UseTaxContainer useTaxContainer : useTaxAccounts) {
1633 
1634             // create summary from items
1635             List<SourceAccountingLine> origSourceAccounts = this.generateSummaryWithNoZeroTotals(useTaxContainer.getItems());
1636             KualiDecimal totalAmount = calculateSumTotal(origSourceAccounts);
1637             List<PurApAccountingLine> accountingLines = generateAccountDistributionForProration(origSourceAccounts, totalAmount, PurapConstants.PRORATION_SCALE, accountingLineClass);
1638 
1639 
1640             List<SourceAccountingLine> newSourceLines = new ArrayList<SourceAccountingLine>();
1641             // convert back to source
1642             convertAmtToTax(accountingLines, useTaxContainer.getUseTax().getTaxAmount(), newSourceLines);
1643 
1644             // do we need an update accounts here?
1645             useTaxContainer.setAccounts(newSourceLines);
1646         }
1647 
1648         useTaxAccounts = new ArrayList<UseTaxContainer>(useTaxItemMap.values());
1649         return useTaxAccounts;
1650     }
1651 
1652     /**
1653      * @see org.kuali.ole.module.purap.service.PurapAccountingService#isTaxAccount(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument,
1654      *      org.kuali.ole.sys.businessobject.SourceAccountingLine)
1655      */
1656     @Override
1657     public boolean isTaxAccount(PurchasingAccountsPayableDocument document, SourceAccountingLine account) {
1658         boolean isTaxAccount = false;
1659 
1660         // check if the summary account is for tax withholding
1661         if (document instanceof PaymentRequestDocument) {
1662             String incomeClassCode = ((PaymentRequestDocument) document).getTaxClassificationCode();
1663             if (StringUtils.isNotEmpty(incomeClassCode)) {
1664 
1665                 String federalChartCode = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX);
1666                 String federalAccountNumber = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX);
1667                 String federalObjectCode = parameterService.getSubParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.FEDERAL_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, incomeClassCode);
1668 
1669                 String stateChartCode = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_CHART_SUFFIX);
1670                 String stateAccountNumber = parameterService.getParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_ACCOUNT_SUFFIX);
1671                 String stateObjectCode = parameterService.getSubParameterValueAsString(PaymentRequestDocument.class, NRATaxParameters.STATE_TAX_PARM_PREFIX + NRATaxParameters.TAX_PARM_OBJECT_BY_INCOME_CLASS_SUFFIX, incomeClassCode);
1672 
1673                 String chartCode = account.getChartOfAccountsCode();
1674                 String accountNumber = account.getAccountNumber();
1675                 String objectCode = account.getFinancialObjectCode();
1676 
1677                 boolean isFederalAccount = StringUtils.equals(federalChartCode, chartCode);
1678                 isFederalAccount &= StringUtils.equals(federalAccountNumber, accountNumber);
1679                 isFederalAccount &= StringUtils.equals(federalObjectCode, objectCode);
1680 
1681                 boolean isStateAccount = StringUtils.equals(stateChartCode, chartCode);
1682                 isStateAccount &= StringUtils.equals(stateAccountNumber, accountNumber);
1683                 isStateAccount &= StringUtils.equals(stateObjectCode, objectCode);
1684 
1685                 isTaxAccount = isFederalAccount || isStateAccount;
1686             }
1687         }
1688 
1689         return isTaxAccount;
1690     }
1691 
1692     public void setParameterService(ParameterService parameterService) {
1693         this.parameterService = parameterService;
1694     }
1695 
1696     public void setPurApAccountingDao(PurApAccountingDao purApAccountingDao) {
1697         this.purApAccountingDao = purApAccountingDao;
1698     }
1699 
1700     public void setPurapService(PurapService purapService) {
1701         this.purapService = purapService;
1702     }
1703 
1704     @Override
1705     public List<SourceAccountingLine> mergeAccountingLineLists(List<SourceAccountingLine> accountingLines1, List<SourceAccountingLine> accountingLines2) {
1706 
1707         KualiDecimal totalAmount = KualiDecimal.ZERO;
1708         List<SourceAccountingLine> mergedAccountList = new ArrayList();
1709 
1710         for (SourceAccountingLine line1 : accountingLines1) {
1711             KualiDecimal line1Amount = KualiDecimal.ZERO;
1712             if (ObjectUtils.isNotNull(line1.getAmount())) {
1713                 line1Amount = line1.getAmount();
1714             }
1715 
1716             for (SourceAccountingLine line2 : accountingLines2) {
1717                 KualiDecimal line2Amount = KualiDecimal.ZERO;
1718                 if (ObjectUtils.isNotNull(line2.getAmount())) {
1719                     line2Amount = line2.getAmount();
1720                 }
1721 
1722                 // if we find a match between lists, then merge amounts
1723                 if (line1.equals(line2)) {
1724                     // add the two amounts
1725                     totalAmount = line1Amount.add(line2Amount);
1726                     line1.setAmount(totalAmount);
1727                 }
1728             }
1729 
1730             mergedAccountList.add(line1);
1731         }
1732 
1733         return mergedAccountList;
1734     }
1735 
1736     /**
1737      * @see org.kuali.ole.module.purap.service.PurapAccountingService#getSummaryAccountsbyPaymentRequestIdentifier(java.lang.Integer)
1738      */
1739     @Override
1740     public List getSummaryAccountsbyPaymentRequestIdentifier(Integer paymentRequestIdentifier) {
1741         if (paymentRequestIdentifier != null) {
1742             Map fieldValues = new HashMap();
1743             fieldValues.put(PurapPropertyConstants.PAYMENT_REQUEST_ID, paymentRequestIdentifier);
1744             return new ArrayList(businessObjectService.findMatching(AccountsPayableSummaryAccount.class, fieldValues));
1745         }
1746         return null;
1747     }
1748 
1749     /**
1750      * @see org.kuali.ole.module.purap.service.PurapAccountingService#getSummaryAccountsbyInvoiceIdentifier(java.lang.Integer)
1751      */
1752     @Override
1753     public List getSummaryAccountsbyInvoiceIdentifier(Integer invoiceIdentifier) {
1754         if (invoiceIdentifier != null) {
1755             Map fieldValues = new HashMap();
1756             fieldValues.put(PurapPropertyConstants.INVOICE_ID, invoiceIdentifier);
1757             return new ArrayList(businessObjectService.findMatching(OleInvoiceAccountsPayableSummaryAccount.class, fieldValues));
1758         }
1759         return null;
1760     }
1761 
1762     /**
1763      * @see org.kuali.ole.module.purap.service.PurapAccountingService#getSummaryAccountsbyCreditMemoIdentifier(java.lang.Integer)
1764      */
1765     @Override
1766     public List getSummaryAccountsbyCreditMemoIdentifier(Integer creditMemoIdentifier) {
1767         if (creditMemoIdentifier != null) {
1768             Map fieldValues = new HashMap();
1769             fieldValues.put(PurapPropertyConstants.CREDIT_MEMO_ID, creditMemoIdentifier);
1770             return new ArrayList(businessObjectService.findMatching(AccountsPayableSummaryAccount.class, fieldValues));
1771         }
1772         return null;
1773     }
1774 
1775     /**
1776      * Sest the businessObjectService.
1777      *
1778      * @param businessObjectService
1779      */
1780     public void setBusinessObjectService(BusinessObjectService businessObjectService) {
1781         this.businessObjectService = businessObjectService;
1782     }
1783 }