86
86
import org .apache .fineract .portfolio .loanaccount .domain .transactionprocessor .AbstractLoanRepaymentScheduleTransactionProcessor ;
87
87
import org .apache .fineract .portfolio .loanaccount .domain .transactionprocessor .MoneyHolder ;
88
88
import org .apache .fineract .portfolio .loanaccount .domain .transactionprocessor .TransactionCtx ;
89
- import org .apache .fineract .portfolio .loanaccount .loanschedule .data .PeriodDueDetails ;
90
- import org .apache .fineract .portfolio .loanaccount .loanschedule .data .ProgressiveLoanInterestScheduleModel ;
91
89
import org .apache .fineract .portfolio .loanaccount .loanschedule .domain .LoanScheduleProcessingType ;
92
90
import org .apache .fineract .portfolio .loanaccount .service .InterestRefundService ;
93
91
import org .apache .fineract .portfolio .loanproduct .calc .EMICalculator ;
92
+ import org .apache .fineract .portfolio .loanproduct .calc .data .PeriodDueDetails ;
93
+ import org .apache .fineract .portfolio .loanproduct .calc .data .ProgressiveLoanInterestScheduleModel ;
94
94
import org .apache .fineract .portfolio .loanproduct .domain .AllocationType ;
95
95
import org .apache .fineract .portfolio .loanproduct .domain .CreditAllocationTransactionType ;
96
96
import org .apache .fineract .portfolio .loanproduct .domain .DueType ;
@@ -421,8 +421,113 @@ protected LoanTransaction findChargebackOriginalTransaction(LoanTransaction char
421
421
return fromTransaction .get ();
422
422
}
423
423
424
- protected void processCreditTransaction (LoanTransaction loanTransaction , TransactionCtx ctx ) {
424
+ private Map <AllocationType , Money > calculateChargebackAllocationMapPrincipalOnly (Money transactionAmount , MonetaryCurrency currency ) {
425
+ Map <AllocationType , Money > chargebackAllocation = new HashMap <>();
426
+ chargebackAllocation .put (PRINCIPAL , transactionAmount );
427
+ chargebackAllocation .put (INTEREST , Money .zero (currency ));
428
+ chargebackAllocation .put (PENALTY , Money .zero (currency ));
429
+ chargebackAllocation .put (FEE , Money .zero (currency ));
430
+ return chargebackAllocation ;
431
+ }
432
+
433
+ protected void processCreditTransactionWithEmiCalculator (LoanTransaction loanTransaction , ProgressiveTransactionCtx ctx ) {
434
+
435
+ ProgressiveLoanInterestScheduleModel model = ctx .getModel ();
436
+ MonetaryCurrency currency = ctx .getCurrency ();
437
+ loanTransaction .resetDerivedComponents ();
438
+ Money transactionAmount = loanTransaction .getAmount (currency );
439
+ Money totalOverpaid = ctx .getOverpaymentHolder ().getMoneyObject ();
440
+ loanTransaction .setOverPayments (totalOverpaid );
441
+ if (!transactionAmount .isGreaterThanZero ()) {
442
+ return ;
443
+ }
444
+ if (!loanTransaction .isChargeback ()) {
445
+ throw new RuntimeException ("Unsupported transaction " + loanTransaction .getTypeOf ().name ());
446
+ }
447
+ Map <AllocationType , Money > chargebackAllocation ;
448
+
425
449
if (hasNoCustomCreditAllocationRule (loanTransaction )) {
450
+ // whole amount should allocate as principal no need to check previous chargebacks.
451
+ chargebackAllocation = calculateChargebackAllocationMapPrincipalOnly (transactionAmount , currency );
452
+ } else {
453
+ chargebackAllocation = calculateChargebackAllocationMapByCreditAllocationRule (loanTransaction , ctx );
454
+ }
455
+
456
+ loanTransaction .updateComponents (chargebackAllocation .get (PRINCIPAL ), chargebackAllocation .get (INTEREST ),
457
+ chargebackAllocation .get (FEE ), chargebackAllocation .get (PENALTY ));
458
+
459
+ if (loanTransaction .getTransactionDate ().isBefore (loanTransaction .getLoan ().getMaturityDate ())) {
460
+ if (chargebackAllocation .get (PRINCIPAL ).isGreaterThanZero ()) {
461
+ emiCalculator .chargebackPrincipal (model , loanTransaction .getTransactionDate (), chargebackAllocation .get (PRINCIPAL ));
462
+ }
463
+
464
+ // interest
465
+ if (chargebackAllocation .get (INTEREST ).isGreaterThanZero ()) {
466
+ emiCalculator .chargebackInterest (model , loanTransaction .getTransactionDate (), chargebackAllocation .get (INTEREST ));
467
+ }
468
+ // update repayment periods until maturity date, for principal and interest portions
469
+ updateRepaymentPeriods (loanTransaction , ctx );
470
+
471
+ LoanRepaymentScheduleInstallment instalment = ctx .getInstallments ().stream ()
472
+ .filter (i -> !loanTransaction .getTransactionDate ().isBefore (i .getFromDate ())
473
+ && i .getDueDate ().isAfter (loanTransaction .getTransactionDate ()))
474
+ .findAny ().orElseThrow ();
475
+ // special because principal and interest dues are already updated.
476
+ recognizeAmountsAfterChargebackWithInterestRecalculation (ctx , instalment , chargebackAllocation );
477
+ } else if (loanTransaction .getTransactionDate ().isAfter (loanTransaction .getLoan ().getMaturityDate ())) {
478
+ // N+1
479
+ LoanRepaymentScheduleInstallment instalment = ctx .getInstallments ().stream ()
480
+ .filter (LoanRepaymentScheduleInstallment ::isAdditional ).findAny ()
481
+ .or (() -> createAdditionalInstalment (loanTransaction , ctx )).orElseThrow ();
482
+ // generic
483
+ recognizeAmountsAfterChargebackWithInterest (ctx , loanTransaction .getTransactionDate (), instalment , chargebackAllocation );
484
+ if (instalment .getDueDate ().isBefore (loanTransaction .getTransactionDate ())) {
485
+ instalment .updateDueDate (loanTransaction .getTransactionDate ());
486
+ }
487
+ } else {
488
+ // last normal instalment (installment due date is the maturity date)
489
+ LoanRepaymentScheduleInstallment instalment = ctx .getInstallments ().stream ()
490
+ .filter (i -> i .getDueDate ().isEqual (loanTransaction .getLoan ().getMaturityDate ())).findAny ().orElseThrow ();
491
+ // generic
492
+ recognizeAmountsAfterChargebackWithInterest (ctx , loanTransaction .getTransactionDate (), instalment , chargebackAllocation );
493
+ }
494
+
495
+ allocateOverpayment (loanTransaction , ctx );
496
+ }
497
+
498
+ private Optional <LoanRepaymentScheduleInstallment > createAdditionalInstalment (LoanTransaction loanTransaction ,
499
+ ProgressiveTransactionCtx ctx ) {
500
+ LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment (loanTransaction .getLoan (),
501
+ (ctx .getInstallments ().size () + 1 ), loanTransaction .getTransactionDate (), loanTransaction .getTransactionDate (), ZERO , ZERO ,
502
+ ZERO , ZERO , false , null );
503
+ installment .markAsAdditional ();
504
+ loanTransaction .getLoan ().addLoanRepaymentScheduleInstallment (installment );
505
+ return Optional .of (installment );
506
+ }
507
+
508
+ private Map <AllocationType , Money > calculateChargebackAllocationMapByCreditAllocationRule (LoanTransaction loanTransaction ,
509
+ ProgressiveTransactionCtx ctx ) {
510
+ MonetaryCurrency currency = ctx .getCurrency ();
511
+ LoanTransaction originalTransaction = findChargebackOriginalTransaction (loanTransaction , ctx );
512
+ // get the original allocation from the opriginal transaction
513
+ Map <AllocationType , Money > originalAllocationNotAdjusted = getOriginalAllocation (originalTransaction , currency );
514
+ LoanCreditAllocationRule chargeBackAllocationRule = getChargebackAllocationRules (loanTransaction );
515
+
516
+ // if there were earlier chargebacks then let's calculate the remaining amounts for each portion
517
+ Map <AllocationType , Money > originalAllocation = adjustOriginalAllocationWithFormerChargebacks (originalTransaction ,
518
+ originalAllocationNotAdjusted , loanTransaction , ctx , chargeBackAllocationRule );
519
+
520
+ // calculate the current chargeback allocation
521
+ return calculateChargebackAllocationMap (originalAllocation , loanTransaction .getAmount (currency ).getAmount (),
522
+ chargeBackAllocationRule .getAllocationTypes (), currency );
523
+ }
524
+
525
+ protected void processCreditTransaction (LoanTransaction loanTransaction , TransactionCtx ctx ) {
526
+ // TODO refactor if needed
527
+ if (loanTransaction .getLoan ().isInterestBearing ()
528
+ && loanTransaction .getLoan ().getLoanProductRelatedDetail ().isInterestRecalculationEnabled ()) {
529
+ processCreditTransactionWithEmiCalculator (loanTransaction , (ProgressiveTransactionCtx ) ctx );
530
+ } else if (hasNoCustomCreditAllocationRule (loanTransaction )) {
426
531
super .processCreditTransaction (loanTransaction , ctx .getOverpaymentHolder (), ctx .getCurrency (), ctx .getInstallments ());
427
532
} else {
428
533
loanTransaction .resetDerivedComponents ();
@@ -433,7 +538,13 @@ protected void processCreditTransaction(LoanTransaction loanTransaction, Transac
433
538
Money transactionAmount = loanTransaction .getAmount (currency );
434
539
Money totalOverpaid = ctx .getOverpaymentHolder ().getMoneyObject ();
435
540
Money amountToDistribute = MathUtil .negativeToZero (transactionAmount ).minus (totalOverpaid );
541
+ // transaction amount should be greater than or equal to 0
542
+ // total overpaid amount should be greater than or equal to 0
543
+ // amountToDistribute = transactionAmount - totalOverpaid
436
544
Money overpaymentAmount = MathUtil .negativeToZero (transactionAmount .minus (amountToDistribute ));
545
+ // overpaymentAmount = negativeToZero ( transactionAmount - (transactionAmount - totalOverpaid) )
546
+ // overpaymentAmount = negativeToZero ( totalOverpaid )
547
+ // TODO does above lines make sense????
437
548
loanTransaction .setOverPayments (overpaymentAmount );
438
549
if (!transactionAmount .isGreaterThanZero ()) {
439
550
return ;
@@ -540,6 +651,26 @@ private Map<AllocationType, Money> adjustOriginalAllocationWithFormerChargebacks
540
651
return allocation ;
541
652
}
542
653
654
+ /*
655
+ private void recognizeFeePenaltiesAmountsAfterChargeback(TransactionCtx ctx, LoanRepaymentScheduleInstallment installment,
656
+ Map<AllocationType, Money> chargebackAllocation) {
657
+ MonetaryCurrency currency = ctx.getCurrency();
658
+ Money fee = chargebackAllocation.get(FEE);
659
+ if (fee.isGreaterThanZero()) {
660
+ installment.addToCreditedFee(fee.getAmount());
661
+ installment.addToChargePortion(fee, Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
662
+ Money.zero(currency));
663
+ }
664
+
665
+ Money penalty = chargebackAllocation.get(PENALTY);
666
+ if (penalty.isGreaterThanZero()) {
667
+ installment.addToCreditedPenalty(penalty.getAmount());
668
+ installment.addToChargePortion(Money.zero(currency), Money.zero(currency), Money.zero(currency), penalty, Money.zero(currency),
669
+ Money.zero(currency));
670
+ }
671
+ }
672
+ */
673
+
543
674
private void recognizeAmountsAfterChargeback (final TransactionCtx ctx , final LocalDate transactionDate ,
544
675
final LoanRepaymentScheduleInstallment installment , final Map <AllocationType , Money > chargebackAllocation ) {
545
676
final Money principal = chargebackAllocation .get (PRINCIPAL );
@@ -570,6 +701,39 @@ private void recognizeAmountsAfterChargeback(final TransactionCtx ctx, final Loc
570
701
}
571
702
}
572
703
704
+ private void recognizeAmountsAfterChargebackWithInterestRecalculation (TransactionCtx ctx , LoanRepaymentScheduleInstallment installment ,
705
+ Map <AllocationType , Money > chargebackAllocation ) {
706
+ Money principal = chargebackAllocation .get (PRINCIPAL );
707
+ if (principal .isGreaterThanZero ()) {
708
+ installment .addToCreditedPrincipal (principal .getAmount ());
709
+ }
710
+ Money interest = chargebackAllocation .get (INTEREST );
711
+ if (principal .isGreaterThanZero ()) {
712
+ installment .addToCreditedInterest (interest .getAmount ());
713
+ }
714
+ recognizeFeePenaltiesAmountsAfterChargeback (ctx , installment , chargebackAllocation );
715
+ }
716
+
717
+ private void recognizeAmountsAfterChargebackWithInterest (TransactionCtx ctx , LocalDate transactionDate ,
718
+ LoanRepaymentScheduleInstallment installment , Map <AllocationType , Money > chargebackAllocation ) {
719
+ Money interest = chargebackAllocation .get (INTEREST );
720
+ if (interest .isGreaterThanZero ()) {
721
+ installment .addToCreditedInterest (interest .getAmount ());
722
+ installment .addToInterest (transactionDate , interest );
723
+ }
724
+ recognizeAmountsAfterChargeback (ctx , transactionDate , installment , chargebackAllocation );
725
+ }
726
+
727
+ private void recognizeAmountsAfterChargeback (TransactionCtx ctx , LocalDate transactionDate ,
728
+ LoanRepaymentScheduleInstallment installment , Map <AllocationType , Money > chargebackAllocation ) {
729
+ Money principal = chargebackAllocation .get (PRINCIPAL );
730
+ if (principal .isGreaterThanZero ()) {
731
+ installment .addToCreditedPrincipal (principal .getAmount ());
732
+ installment .addToPrincipal (transactionDate , principal );
733
+ }
734
+ recognizeFeePenaltiesAmountsAfterChargeback (ctx , installment , chargebackAllocation );
735
+ }
736
+
573
737
@ NotNull
574
738
private LoanCreditAllocationRule getChargebackAllocationRules (LoanTransaction loanTransaction ) {
575
739
return loanTransaction .getLoan ().getCreditAllocationRules ().stream ()
0 commit comments