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