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 }