001/*
002 * Copyright 2007 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.module.purap.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.ole.module.purap.PurapConstants;
020import org.kuali.ole.module.purap.PurapConstants.PurapDocTypeCodes;
021import org.kuali.ole.module.purap.PurapKeyConstants;
022import org.kuali.ole.module.purap.PurapParameterConstants.NRATaxParameters;
023import org.kuali.ole.module.purap.PurapPropertyConstants;
024import org.kuali.ole.module.purap.businessobject.*;
025import org.kuali.ole.module.purap.dataaccess.PurApAccountingDao;
026import org.kuali.ole.module.purap.document.InvoiceDocument;
027import org.kuali.ole.module.purap.document.PaymentRequestDocument;
028import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument;
029import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocumentBase;
030import org.kuali.ole.module.purap.document.service.PurapService;
031import org.kuali.ole.module.purap.service.PurapAccountingService;
032import org.kuali.ole.module.purap.util.PurApItemUtils;
033import org.kuali.ole.module.purap.util.PurApObjectUtils;
034import org.kuali.ole.module.purap.util.SummaryAccount;
035import org.kuali.ole.module.purap.util.UseTaxContainer;
036import org.kuali.ole.select.businessobject.*;
037import org.kuali.ole.select.document.OleInvoiceDocument;
038import org.kuali.ole.select.document.OlePaymentRequestDocument;
039import org.kuali.ole.select.document.OleVendorCreditMemoDocument;
040import org.kuali.ole.sys.businessobject.AccountingLineBase;
041import org.kuali.ole.sys.businessobject.SourceAccountingLine;
042import org.kuali.ole.sys.context.SpringContext;
043import org.kuali.ole.sys.service.NonTransactional;
044import org.kuali.rice.core.api.util.type.KualiDecimal;
045import org.kuali.rice.coreservice.framework.parameter.ParameterService;
046import org.kuali.rice.krad.service.BusinessObjectService;
047import org.kuali.rice.krad.service.KualiRuleService;
048import org.kuali.rice.krad.util.GlobalVariables;
049import org.kuali.rice.krad.util.ObjectUtils;
050
051import java.math.BigDecimal;
052import java.util.*;
053
054/**
055 * Contains a number of helper methods to deal with accounts on Purchasing Accounts Payable Documents
056 */
057
058@NonTransactional
059public class PurapAccountingServiceImpl implements PurapAccountingService {
060    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapAccountingServiceImpl.class);
061
062    protected static final BigDecimal ONE_HUNDRED = new BigDecimal(100);
063    protected static final int SCALE = 340;
064    protected static final int BIG_DECIMAL_ROUNDING_MODE = BigDecimal.ROUND_HALF_UP;
065    protected static final int BIG_DECIMAL_SCALE = 2;
066
067    // local constants
068    protected static final Boolean ITEM_TYPES_INCLUDED_VALUE = Boolean.TRUE;
069    ;
070    protected static final Boolean ITEM_TYPES_EXCLUDED_VALUE = Boolean.FALSE;
071    protected static final Boolean ZERO_TOTALS_RETURNED_VALUE = Boolean.TRUE;
072    protected static final Boolean ZERO_TOTALS_NOT_RETURNED_VALUE = Boolean.FALSE;
073    protected static final Boolean ALTERNATE_AMOUNT_USED = Boolean.TRUE;
074    protected static final Boolean ALTERNATE_AMOUNT_NOT_USED = Boolean.FALSE;
075    protected static final Boolean USE_TAX_INCLUDED = Boolean.TRUE;
076    protected static final Boolean USE_TAX_EXCLUDED = Boolean.FALSE;
077
078    protected ParameterService parameterService;
079    protected PurapService purapService;
080    protected PurApAccountingDao purApAccountingDao;
081    protected BusinessObjectService businessObjectService;
082    private OlePaymentRequestDocument olePaymentRequestDocument;
083
084    /**
085     * gets the lowest possible number for rounding, it works for ROUND_HALF_UP
086     *
087     * @return a BigDecimal representing the lowest possible number for rounding
088     */
089    protected BigDecimal getLowestPossibleRoundUpNumber() {
090        BigDecimal startingDigit = new BigDecimal(0.5);
091        if (SCALE != 0) {
092            startingDigit = startingDigit.movePointLeft(SCALE);
093        }
094        return startingDigit;
095    }
096
097    /**
098     * Helper method to log and throw an error
099     *
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}