Skip to content

Commit 7fab52a

Browse files
committed
FINERACT-2162: Chargeback interest bearing loans
1 parent 7d0474c commit 7fab52a

File tree

21 files changed

+1310
-82
lines changed

21 files changed

+1310
-82
lines changed

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java

+8
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,14 @@ public void addToInterest(final LocalDate transactionDate, final Money transacti
853853
checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
854854
}
855855

856+
public void addToCreditedInterest(final BigDecimal amount) {
857+
if (this.creditedInterest == null) {
858+
this.creditedInterest = amount;
859+
} else {
860+
this.creditedInterest = this.creditedInterest.add(amount);
861+
}
862+
}
863+
856864
public void addToCreditedPrincipal(final BigDecimal amount) {
857865
if (this.creditedPrincipal == null) {
858866
this.creditedPrincipal = amount;

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java

+167-3
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,11 @@
8686
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
8787
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
8888
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;
9189
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
9290
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
9391
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;
9494
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
9595
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
9696
import org.apache.fineract.portfolio.loanproduct.domain.DueType;
@@ -421,8 +421,113 @@ protected LoanTransaction findChargebackOriginalTransaction(LoanTransaction char
421421
return fromTransaction.get();
422422
}
423423

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+
425449
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)) {
426531
super.processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments());
427532
} else {
428533
loanTransaction.resetDerivedComponents();
@@ -433,7 +538,13 @@ protected void processCreditTransaction(LoanTransaction loanTransaction, Transac
433538
Money transactionAmount = loanTransaction.getAmount(currency);
434539
Money totalOverpaid = ctx.getOverpaymentHolder().getMoneyObject();
435540
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
436544
Money overpaymentAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
545+
// overpaymentAmount = negativeToZero ( transactionAmount - (transactionAmount - totalOverpaid) )
546+
// overpaymentAmount = negativeToZero ( totalOverpaid )
547+
// TODO does above lines make sense????
437548
loanTransaction.setOverPayments(overpaymentAmount);
438549
if (!transactionAmount.isGreaterThanZero()) {
439550
return;
@@ -540,6 +651,26 @@ private Map<AllocationType, Money> adjustOriginalAllocationWithFormerChargebacks
540651
return allocation;
541652
}
542653

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+
543674
private void recognizeAmountsAfterChargeback(final TransactionCtx ctx, final LocalDate transactionDate,
544675
final LoanRepaymentScheduleInstallment installment, final Map<AllocationType, Money> chargebackAllocation) {
545676
final Money principal = chargebackAllocation.get(PRINCIPAL);
@@ -570,6 +701,39 @@ private void recognizeAmountsAfterChargeback(final TransactionCtx ctx, final Loc
570701
}
571702
}
572703

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+
573737
@NotNull
574738
private LoanCreditAllocationRule getChargebackAllocationRules(LoanTransaction loanTransaction) {
575739
return loanTransaction.getLoan().getCreditAllocationRules().stream()

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/ProgressiveTransactionCtx.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
3333
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
3434
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
35-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
35+
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
3636

3737
@Getter
3838
public class ProgressiveTransactionCtx extends TransactionCtx {

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@
4848
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
4949
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
5050
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan;
51-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails;
52-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
5351
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
5452
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
53+
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
54+
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
5555
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
5656
import org.springframework.stereotype.Component;
5757

0 commit comments

Comments
 (0)