View Javadoc

1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.select.document.service.impl;
17  
18  import org.kuali.ole.module.purap.PurapConstants;
19  import org.kuali.ole.module.purap.businessobject.PurApAccountingLine;
20  import org.kuali.ole.module.purap.businessobject.PurApItem;
21  import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument;
22  import org.kuali.ole.module.purap.document.service.PurapService;
23  import org.kuali.ole.module.purap.service.impl.PurapAccountingServiceImpl;
24  import org.kuali.ole.module.purap.util.PurApItemUtils;
25  import org.kuali.ole.module.purap.util.PurApObjectUtils;
26  import org.kuali.ole.select.businessobject.OlePaymentRequestItem;
27  import org.kuali.ole.select.businessobject.OlePurchaseOrderItem;
28  import org.kuali.ole.select.document.service.OlePurapAccountingService;
29  import org.kuali.ole.sys.businessobject.AccountingLineBase;
30  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
31  import org.kuali.ole.sys.service.NonTransactional;
32  import org.kuali.rice.core.api.util.type.KualiDecimal;
33  import org.kuali.rice.krad.util.ObjectUtils;
34  import  org.kuali.ole.select.businessobject.OleInvoiceItem;
35  
36  import java.math.BigDecimal;
37  import java.math.RoundingMode;
38  import java.util.*;
39  
40  /**
41   * Contains a number of helper methods to deal with accounts on Purchasing Accounts Payable Documents
42   */
43  
44  @NonTransactional
45  public class OlePurapAccountingServiceImpl extends PurapAccountingServiceImpl implements OlePurapAccountingService {
46      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OlePurapAccountingServiceImpl.class);
47  
48      private PurapService purapService;
49  
50  
51      @Override
52      public void setPurapService(PurapService purapService) {
53          this.purapService = purapService;
54      }
55  
56      /**
57       * calculates values for a list of accounting lines based on an amount
58       *
59       * @param sourceAccountingLines
60       * @param totalAmount
61       */
62      @Override
63      public <T extends PurApAccountingLine> void updateAccountAmountsWithTotal(List<T> sourceAccountingLines, KualiDecimal totalAmount) {
64          if ((totalAmount != null) && KualiDecimal.ZERO.compareTo(totalAmount) != 0) {
65  
66              KualiDecimal accountTotal = KualiDecimal.ZERO;
67              T lastAccount = null;
68  
69  
70              for (T account : sourceAccountingLines) {
71                  if (ObjectUtils.isNotNull(account.getAccountLinePercent())) {
72                      BigDecimal pct = new BigDecimal(account.getAccountLinePercent().toString()).divide(new BigDecimal(100));
73                      account.setAmount(new KualiDecimal(pct.multiply(new BigDecimal(totalAmount.toString())).setScale(KualiDecimal.SCALE, KualiDecimal.ROUND_BEHAVIOR)));
74                  } else if (ObjectUtils.isNotNull(account.getAmount()) && ObjectUtils.isNull(account.getAccountLinePercent())) {
75                      KualiDecimal dollar = account.getAmount().multiply(new KualiDecimal(100));
76                      BigDecimal dollarToPercent = dollar.bigDecimalValue().divide((totalAmount.bigDecimalValue()), 0, RoundingMode.FLOOR);
77                      account.setAccountLinePercent(dollarToPercent);
78                  } else if (ObjectUtils.isNotNull(account.getAmount()) && ObjectUtils.isNotNull(account.getAccountLinePercent())) {
79                      account.setAmount(account.getAmount());
80                      account.setAccountLinePercent(account.getAccountLinePercent());
81                  } else {
82                      account.setAmount(KualiDecimal.ZERO);
83                  }
84                  accountTotal = accountTotal.add(account.getAmount());
85                  lastAccount = account;
86              }
87  
88          } else {
89              // zero out if extended price is zero
90              for (T account : sourceAccountingLines) {
91                  account.setAmount(KualiDecimal.ZERO);
92              }
93          }
94      }
95  
96      /**
97       * @see org.kuali.ole.module.purap.service.PurapAccountingService#generateAccountDistributionForProration(java.util.List,
98       *      org.kuali.rice.kns.util.KualiDecimal, java.lang.Integer)
99       */
100     @Override
101     public List<PurApAccountingLine> generateAccountDistributionForProration(List<SourceAccountingLine> accounts, KualiDecimal totalAmount, Integer percentScale, Class clazz) {
102         String methodName = "generateAccountDistributionForProration()";
103         if (LOG.isDebugEnabled()) {
104             LOG.debug(methodName + " started");
105         }
106         List<PurApAccountingLine> newAccounts = new ArrayList();
107 
108         if (totalAmount.isZero()) {
109             throwRuntimeException(methodName, "Purchasing/Accounts Payable account distribution for proration does not allow zero dollar total.");
110         }
111 
112         BigDecimal percentTotal = BigDecimal.ZERO;
113         BigDecimal totalAmountBigDecimal = totalAmount.bigDecimalValue();
114         for (SourceAccountingLine accountingLine : accounts) {
115             if (LOG.isDebugEnabled()) {
116                 LOG.debug(methodName + " " + accountingLine.getAccountNumber() + " " + accountingLine.getAmount() + "/" + totalAmountBigDecimal);
117             }
118             //  BigDecimal pct = accountingLine.getAmount().bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE);
119             BigDecimal pct = BigDecimal.ZERO;
120             if (accountingLine.getSourceAcountLinePercent() != null)
121                 pct = accountingLine.getSourceAcountLinePercent();
122             else
123                 pct = accountingLine.getAmount().bigDecimalValue().divide(totalAmountBigDecimal, percentScale, BIG_DECIMAL_ROUNDING_MODE);
124             pct = pct.stripTrailingZeros().multiply(ONE_HUNDRED);
125 
126             if (LOG.isDebugEnabled()) {
127                 LOG.debug(methodName + " pct = " + pct + "  (trailing zeros removed)");
128             }
129 
130             BigDecimal lowestPossible = this.getLowestPossibleRoundUpNumber();
131             if (lowestPossible.compareTo(pct) <= 0) {
132                 PurApAccountingLine newAccountingLine;
133                 newAccountingLine = null;
134 
135                 try {
136                     newAccountingLine = (PurApAccountingLine) clazz.newInstance();
137                 } catch (InstantiationException e) {
138                     e.printStackTrace();
139                 } catch (IllegalAccessException e) {
140                     e.printStackTrace();
141                 }
142 
143                 PurApObjectUtils.populateFromBaseClass(AccountingLineBase.class, accountingLine, newAccountingLine);
144                 newAccountingLine.setAccountLinePercent(pct);
145                 if (LOG.isDebugEnabled()) {
146                     LOG.debug(methodName + " adding " + newAccountingLine.getAccountLinePercent());
147                 }
148                 newAccounts.add(newAccountingLine);
149                 percentTotal = percentTotal.add(newAccountingLine.getAccountLinePercent());
150                 if (LOG.isDebugEnabled()) {
151                     LOG.debug(methodName + " total = " + percentTotal);
152                 }
153             }
154         }
155 
156         if ((percentTotal.compareTo(BigDecimal.ZERO)) == 0) {
157            /*
158             * This means there are so many accounts or so strange a distribution that we can't round properly... not sure of viable
159             * solution
160             */
161             throwRuntimeException(methodName, "Can't round properly due to number of accounts");
162         }
163 
164         // Now deal with rounding
165         if ((ONE_HUNDRED.compareTo(percentTotal)) < 0) {
166            /*
167             * The total percent is greater than one hundred Here we find the account that occurs latest in our list with a percent
168             * that is higher than the difference and we subtract off the difference
169             */
170             BigDecimal difference = percentTotal.subtract(ONE_HUNDRED);
171             if (LOG.isDebugEnabled()) {
172                 LOG.debug(methodName + " Rounding up by " + difference);
173             }
174 
175             boolean foundAccountToUse = false;
176             int currentNbr = newAccounts.size() - 1;
177             while (currentNbr >= 0) {
178                 PurApAccountingLine potentialSlushAccount = (PurApAccountingLine) newAccounts.get(currentNbr);
179 
180                 if ((difference.compareTo(potentialSlushAccount.getAccountLinePercent())) < 0) {
181                     // the difference amount is less than the current accounts percent... use this account
182                     // the 'potentialSlushAccount' technically is now the true 'Slush Account'
183                     potentialSlushAccount.setAccountLinePercent(potentialSlushAccount.getAccountLinePercent().subtract(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2));
184                     foundAccountToUse = true;
185                     break;
186                 }
187                 currentNbr--;
188             }
189 
190             if (!foundAccountToUse) {
191                /*
192                 * We could not find any account in our list where the percent of that account was greater than that of the
193                 * difference... doing so on just any account could result in a negative percent value
194                 */
195                 throwRuntimeException(methodName, "Can't round properly due to math calculation error");
196             }
197 
198         } else if ((ONE_HUNDRED.compareTo(percentTotal)) > 0) {
199            /*
200             * The total percent is less than one hundred Here we find the last account in our list and add the remaining required
201             * percent to its already calculated percent
202             */
203             BigDecimal difference = ONE_HUNDRED.subtract(percentTotal);
204             if (LOG.isDebugEnabled()) {
205                 LOG.debug(methodName + " Rounding down by " + difference);
206             }
207             PurApAccountingLine slushAccount = (PurApAccountingLine) newAccounts.get(newAccounts.size() - 1);
208             slushAccount.setAccountLinePercent(slushAccount.getAccountLinePercent().add(difference).movePointLeft(2).stripTrailingZeros().movePointRight(2));
209         }
210         if (LOG.isDebugEnabled()) {
211             LOG.debug(methodName + " ended");
212         }
213         return newAccounts;
214     }
215 
216     /**
217      * Generates an account summary, that is it creates a list of source accounts by rounding up the purap accounts off of the purap
218      * items.
219      *
220      * @param items                    the items to determ
221      * @param itemTypeCodes            the item types to determine whether to look at an item in combination with itemTypeCodesAreIncluded
222      * @param itemTypeCodesAreIncluded value to tell whether the itemTypeCodes parameter lists inclusion or exclusion variables
223      * @param useZeroTotals            whether to include items with a zero dollar total
224      * @param useAlternateAmount       an alternate amount used in certain cases for GL entry
225      * @return a list of source accounts
226      */
227     @Override
228     protected List<SourceAccountingLine> generateAccountSummary(List<PurApItem> items, Set<String> itemTypeCodes, Boolean itemTypeCodesAreIncluded, Boolean useZeroTotals, Boolean useAlternateAmount, Boolean useTaxIncluded, Boolean taxableOnly) {
229         BigDecimal addChargeItem = BigDecimal.ZERO;
230         for (PurApItem item : items) {
231             if (!item.getItemType().isQuantityBasedGeneralLedgerIndicator() && !item.getItemTypeCode().equalsIgnoreCase(PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE) && item.getItemUnitPrice() != null) {
232                 addChargeItem = addChargeItem.add(item.getItemUnitPrice());
233             }
234         }
235         items = PurApItemUtils.getAboveTheLineOnly(items);
236         List<PurApItem> itemsToProcess = getProcessablePurapItems(items, itemTypeCodes, itemTypeCodesAreIncluded, useZeroTotals);
237         Map<PurApAccountingLine, KualiDecimal> accountMap = new HashMap<PurApAccountingLine, KualiDecimal>();
238         Map<PurApAccountingLine, KualiDecimal> prorateMap = new HashMap<PurApAccountingLine, KualiDecimal>();
239 
240         for (PurApItem currentItem : itemsToProcess) {
241             if (PurApItemUtils.checkItemActive(currentItem)) {
242                 List<PurApAccountingLine> sourceAccountingLines = currentItem.getSourceAccountingLines();
243 
244                 // skip if item is not taxable and taxable only flag has been set
245                 if (taxableOnly) {
246                     PurchasingAccountsPayableDocument document = currentItem.getPurapDocument();
247                     if (!purapService.isTaxableForSummary(document.isUseTaxIndicator(), purapService.getDeliveryState(document), currentItem)) {
248                         continue;
249                     }
250                 }
251 
252                 if (!useTaxIncluded) {
253                     // if no use tax set the source accounting lines to a clone so we can update
254                     // them to be based on the non tax amount
255                     PurApItem cloneItem = (PurApItem) ObjectUtils.deepCopy(currentItem);
256                     sourceAccountingLines = cloneItem.getSourceAccountingLines();
257                     updateAccountAmountsWithTotal(sourceAccountingLines, currentItem.getTotalRemitAmount());
258                 }
259 
260                 for (PurApAccountingLine account : sourceAccountingLines) {
261 
262                     // skip account if not taxable and taxable only flag is set
263                     if (taxableOnly) {
264                         PurchasingAccountsPayableDocument document = currentItem.getPurapDocument();
265                         // check if account is not taxable, if not skip this account
266                         if (!purapService.isAccountingLineTaxable(account, purapService.isDeliveryStateTaxable(purapService.getDeliveryState(document)))) {
267                             continue;
268                         }
269                     }
270 
271                     // getting the total to set on the account
272                     KualiDecimal total = KualiDecimal.ZERO;
273                     KualiDecimal prorateTotal = KualiDecimal.ZERO;
274                     if (accountMap.containsKey(account)) {
275                         total = accountMap.get(account);
276                         prorateTotal = prorateMap.get(account);
277                     }
278 
279                     if (useAlternateAmount) {
280                         total = total.add(account.getAlternateAmountForGLEntryCreation());
281                     } else {
282                         total = total.add(account.getAmount());
283                     }
284                     if ((currentItem instanceof OlePaymentRequestItem) &&
285                             currentItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
286                         OlePaymentRequestItem item = (OlePaymentRequestItem) currentItem;
287                         prorateTotal = prorateTotal.add(item.getItemQuantity().multiply(new KualiDecimal(item.getItemSurcharge())));
288                     }
289                     else if ((currentItem instanceof OleInvoiceItem) &&
290                             currentItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
291                         OleInvoiceItem item = (OleInvoiceItem) currentItem;
292                         prorateTotal = prorateTotal.add(item.getItemQuantity().multiply(new KualiDecimal(item.getItemSurcharge())));
293                     }
294                     else if ((currentItem instanceof OlePurchaseOrderItem) &&
295                             currentItem.getItemType().isQuantityBasedGeneralLedgerIndicator()) {
296                         OlePurchaseOrderItem item = (OlePurchaseOrderItem) currentItem;
297                         prorateTotal = prorateTotal.add(item.getItemQuantity().multiply(new KualiDecimal(item.getItemSurcharge())));
298                     }
299                     accountMap.put(account, total);
300                     prorateMap.put(account, prorateTotal);
301                 }
302             }
303         }
304 
305         // convert list of PurApAccountingLine objects to SourceAccountingLineObjects
306         Iterator<PurApAccountingLine> iterator = accountMap.keySet().iterator();
307         List<SourceAccountingLine> sourceAccounts = new ArrayList<SourceAccountingLine>();
308         for (Iterator<PurApAccountingLine> iter = iterator; iter.hasNext(); ) {
309             PurApAccountingLine accountToConvert = (PurApAccountingLine) iter.next();
310             if (accountToConvert.isEmpty()) {
311                 String errorMessage = "Found an 'empty' account in summary generation " + accountToConvert.toString();
312                 LOG.error("generateAccountSummary() " + errorMessage);
313                 throw new RuntimeException(errorMessage);
314             }
315             KualiDecimal sourceLineTotal = accountMap.get(accountToConvert);
316             SourceAccountingLine sourceLine = accountToConvert.generateSourceAccountingLine();
317             sourceLine.setAmount(sourceLineTotal);
318             if (addChargeItem.compareTo(BigDecimal.ZERO) != 0) {
319                 sourceLine.setSourceAcountLinePercent(prorateMap.get(accountToConvert).bigDecimalValue().divide(addChargeItem, new Integer(6), BIG_DECIMAL_ROUNDING_MODE));
320             }
321             sourceAccounts.add(sourceLine);
322         }
323 
324         // sort the sourceAccounts list first by account number, then by object code, ignoring chart code
325         Collections.sort(sourceAccounts, new Comparator<SourceAccountingLine>() {
326             @Override
327             public int compare(SourceAccountingLine sal1, SourceAccountingLine sal2) {
328                 int compare = 0;
329                 if (sal1 != null && sal2 != null) {
330                     if (sal1.getAccountNumber() != null && sal2.getAccountNumber() != null) {
331                         compare = sal1.getAccountNumber().compareTo(sal2.getAccountNumber());
332                         if (compare == 0) {
333                             if (sal1.getFinancialObjectCode() != null && sal2.getFinancialObjectCode() != null)
334                                 compare = sal1.getFinancialObjectCode().compareTo(sal2.getFinancialObjectCode());
335                         }
336                     }
337                 }
338                 return compare;
339             }
340         });
341 
342         return sourceAccounts;
343     }
344 
345 }