Skip to content

Commit f7332d8

Browse files
committed
FINERACT-2162: Chargeback interest bearing loans
Added tests to cover the charbeback cases where no allocation rule is set but interest recalc is enabled
1 parent 7d0474c commit f7332d8

File tree

23 files changed

+2261
-148
lines changed

23 files changed

+2261
-148
lines changed

fineract-e2e-tests-runner/src/test/resources/features/LoanChargeback.feature

+276
Large diffs are not rendered by default.

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

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

856-
public void addToCreditedPrincipal(final BigDecimal amount) {
857-
if (this.creditedPrincipal == null) {
858-
this.creditedPrincipal = amount;
859-
} else {
860-
this.creditedPrincipal = this.creditedPrincipal.add(amount);
861-
}
862-
}
863-
864856
public void addToCreditedInterest(final BigDecimal amount) {
865857
if (this.creditedInterest == null) {
866858
this.creditedInterest = amount;
@@ -869,6 +861,14 @@ public void addToCreditedInterest(final BigDecimal amount) {
869861
}
870862
}
871863

864+
public void addToCreditedPrincipal(final BigDecimal amount) {
865+
if (this.creditedPrincipal == null) {
866+
this.creditedPrincipal = amount;
867+
} else {
868+
this.creditedPrincipal = this.creditedPrincipal.add(amount);
869+
}
870+
}
871+
872872
public void addToCreditedFee(final BigDecimal amount) {
873873
if (this.creditedFee == null) {
874874
this.creditedFee = amount;

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java

+6-11
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,12 @@ public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final Loa
6666
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
6767
existingTransactionIds.addAll(loan.findExistingTransactionIds());
6868
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
69-
/*
70-
* LocalDate recalculateFrom = null; List<LoanTransaction> loanTransactions =
71-
* this.retrieveListOfTransactionsPostDisbursementExcludeAccruals(); for (LoanTransaction loanTransaction :
72-
* loanTransactions) { if (recalculateFrom == null ||
73-
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { recalculateFrom =
74-
* loanTransaction.getTransactionDate(); } } generatorDTO.setRecalculateFrom(recalculateFrom);
75-
*/
76-
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
77-
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
78-
} else {
79-
regenerateRepaymentSchedule(loan, generatorDTO);
69+
if (!loan.isProgressiveSchedule()) {
70+
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && !loan.isChargedOff()) {
71+
regenerateRepaymentScheduleWithInterestRecalculation(loan, generatorDTO);
72+
} else {
73+
regenerateRepaymentSchedule(loan, generatorDTO);
74+
}
8075
}
8176
return loan.reprocessTransactions();
8277
}

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

+161-30
Large diffs are not rendered by default.

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

+12-6
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

@@ -182,9 +182,15 @@ public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency
182182
.principal(outstandingAmounts.getOutstandingPrincipal()) //
183183
.interest(outstandingAmounts.getOutstandingInterest());//
184184

185-
installments.forEach(installment -> result //
186-
.plusFeeCharges(installment.getFeeChargesOutstanding(currency))
187-
.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency)));
185+
installments.forEach(installment -> {
186+
if (installment.isAdditional()) {
187+
result.plusPrincipal(installment.getPrincipalOutstanding(currency))
188+
.plusInterest(installment.getInterestOutstanding(currency));
189+
}
190+
result //
191+
.plusFeeCharges(installment.getFeeChargesOutstanding(currency))
192+
.plusPenaltyCharges(installment.getPenaltyChargesOutstanding(currency));
193+
});
188194

189195
return result;
190196
}
@@ -200,7 +206,7 @@ public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment
200206
return Money.zero(loan.getCurrency());
201207
}
202208
ProgressiveLoanInterestScheduleModel model = processor.calculateInterestScheduleModel(loan.getId(), targetDate);
203-
return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate);
209+
return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate, false);
204210
}
205211

206212
// Private, internal methods

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java

+58-5
Original file line numberDiff line numberDiff line change
@@ -27,49 +27,98 @@
2727
import org.apache.fineract.organisation.monetary.domain.Money;
2828
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
2929
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
30-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OutstandingDetails;
31-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.PeriodDueDetails;
32-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.ProgressiveLoanInterestScheduleModel;
33-
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.RepaymentPeriod;
3430
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelRepaymentPeriod;
31+
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
32+
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
33+
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
34+
import org.apache.fineract.portfolio.loanproduct.calc.data.RepaymentPeriod;
3535
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
3636

3737
public interface EMICalculator {
3838

39+
/**
40+
* This method creates an Interest model with repayment periods from the schedule periods which generated by
41+
* schedule generator.
42+
*/
3943
@NotNull
4044
ProgressiveLoanInterestScheduleModel generatePeriodInterestScheduleModel(@NotNull List<LoanScheduleModelRepaymentPeriod> periods,
4145
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
4246
List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc);
4347

48+
/**
49+
* This method creates an Interest model with repayment periods from the installments which retrieved from the
50+
* database.
51+
*/
4452
@NotNull
4553
ProgressiveLoanInterestScheduleModel generateInstallmentInterestScheduleModel(
4654
@NotNull List<LoanRepaymentScheduleInstallment> installments,
4755
@NotNull LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
4856
List<LoanTermVariationsData> loanTermVariations, Integer installmentAmountInMultiplesOf, MathContext mc);
4957

58+
/**
59+
* Find repayment period based on Due Date.
60+
*/
5061
Optional<RepaymentPeriod> findRepaymentPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate dueDate);
5162

63+
/**
64+
* Applies the Bank disbursement on the interest model. This method recalculates the EMI amounts from the action
65+
* date.
66+
*/
5267
void addDisbursement(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate disbursementDueDate, Money disbursedAmount);
5368

69+
/**
70+
* Applies the interest rate change on the interest model. This method recalculates the EMI amounts from the action
71+
* date.
72+
*/
5473
void changeInterestRate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate newInterestSubmittedOnDate,
5574
BigDecimal newInterestRate);
5675

76+
/**
77+
* This method applies outstanding balance correction on the interest model. Negative amount decreases the
78+
* outstanding balance while positive amounts are increasing that. Typically used for late repayment or to count
79+
* repayments.
80+
*/
5781
void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
5882
Money balanceCorrectionAmount);
5983

84+
/**
85+
* This method used for pay interest portion during the repayment transaction.
86+
*/
6087
void payInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
6188
Money interestAmount);
6289

90+
/**
91+
* This method used for pay principal portion during the repayment transaction.
92+
*/
6393
void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate repaymentPeriodDueDate, LocalDate transactionDate,
6494
Money principalAmount);
6595

96+
/**
97+
* This method used for charge back principal portion. This method increases the outstanding balance. This method
98+
* creates a calculated "virtual" EMI for the applied period.
99+
*/
100+
void chargebackPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate,
101+
Money chargebackPrincipalAmount);
102+
103+
/**
104+
* This method used for charge back interest portion. This method adds extra interest due. This method creates a
105+
* calculated "virtual" EMI for the applied period.
106+
*/
107+
void chargebackInterest(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money chargebackInterestAmount);
108+
109+
/**
110+
* This method gives back the maximum of the due principal and maximum of the due interest for a requested day.
111+
*/
66112
@NotNull
67113
PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
68114
@NotNull LocalDate targetDate);
69115

116+
/**
117+
* Gives back the sum of the interest from the whole model on the given date.
118+
*/
70119
@NotNull
71120
Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate,
72-
@NotNull LocalDate targetDate);
121+
@NotNull LocalDate targetDate, boolean includeChargebackInterest);
73122

74123
Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate repaymentPeriodDueDate,
75124
LocalDate targetDate);
@@ -78,5 +127,9 @@ Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel int
78127

79128
Money getSumOfDueInterestsOnDate(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate subjectDate);
80129

130+
/**
131+
* This method stops the interest counting for the given range. Chargeback interest counts even if the normal
132+
* interest paused.
133+
*/
81134
void applyInterestPause(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate fromDate, LocalDate endDate);
82135
}

0 commit comments

Comments
 (0)