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