Skip to content

Commit 698893c

Browse files
committed
FINERACT-2221: Fix - Interest not counted towards totalUnpaidPayableNotDueInterest after partial repayment
1 parent 8462c67 commit 698893c

File tree

9 files changed

+186
-42
lines changed

9 files changed

+186
-42
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency
182182
.principal(outstandingAmounts.getOutstandingPrincipal()) //
183183
.interest(outstandingAmounts.getOutstandingInterest());//
184184

185+
// We need to deduct any paid amount if there is no interest recalculation
186+
if (!loan.isInterestRecalculationEnabled()) {
187+
BigDecimal paidInterest = installments.stream().map(LoanRepaymentScheduleInstallment::getInterestPaid).reduce(BigDecimal.ZERO,
188+
BigDecimal::add);
189+
BigDecimal paidPrincipal = installments.stream().map(LoanRepaymentScheduleInstallment::getPrincipal).reduce(BigDecimal.ZERO,
190+
BigDecimal::add);
191+
result.principal().minus(paidPrincipal);
192+
result.interest().minus(paidInterest);
193+
}
194+
185195
installments.forEach(installment -> {
186196
if (installment.isAdditional()) {
187197
result.plusPrincipal(installment.getPrincipalOutstanding(currency))

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ private InterestPeriod findPreviousInterestPeriod(final RepaymentPeriod repaymen
298298
* @return
299299
*/
300300
public Money getTotalDueInterest() {
301-
return repaymentPeriods().stream().map(RepaymentPeriod::getCalculatedDueInterest).reduce(zero(), Money::plus);
301+
return repaymentPeriods().stream().map(RepaymentPeriod::getDueInterest).reduce(zero(), Money::plus);
302302
}
303303

304304
/**

fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculatorTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,10 +1302,9 @@ public void test_dailyInterest_chargeback_disbursedAmt1000_dayInYears360_daysInM
13021302
final LocalDate dueDate = LocalDate.of(2024, 2, 1);
13031303
final LocalDate startDay = LocalDate.of(2024, 1, 1);
13041304

1305-
// TODO: work on interest calculation
1306-
// emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56));
1307-
// emiCalculator.chargebackInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
1308-
// emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0));
1305+
emiCalculator.payInterest(interestModel, dueDate, startDay.plusDays(3), toMoney(0.56));
1306+
emiCalculator.chargebackInterest(interestModel, startDay.plusDays(3), toMoney(0.0));
1307+
emiCalculator.addBalanceCorrection(interestModel, startDay.plusDays(3), toMoney(0.0));
13091308

13101309
checkDailyInterest(interestModel, dueDate, startDay, 1, 0.19, 0.19);
13111310
checkDailyInterest(interestModel, dueDate, startDay, 2, 0.19, 0.38);

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,13 @@ public LoanSummaryData withTransactionAmountsSummary(Loan loan, LoanSummaryData
8787
totalRepaymentTransactionReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
8888
LoanTransactionType.REPAYMENT);
8989

90-
if (repaymentSchedule != null) {
90+
if (repaymentSchedule != null && defaultSummaryData.getInterestCharged().compareTo(BigDecimal.ZERO) > 0) {
9191
// Outstanding Interest on Past due installments
9292
totalUnpaidPayableDueInterest = computeTotalUnpaidPayableDueInterestAmount(repaymentSchedule.getPeriods(), businessDate);
9393

9494
// Accumulated daily interest of the current Installment period
9595
totalUnpaidPayableNotDueInterest = computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(loan,
96-
repaymentSchedule.getPeriods(), businessDate, defaultSummaryData.getCurrency());
96+
repaymentSchedule.getPeriods(), businessDate, defaultSummaryData.getCurrency(), totalUnpaidPayableDueInterest);
9797
}
9898

9999
return LoanSummaryData.builder().currency(defaultSummaryData.getCurrency())

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CumulativeLoanSummaryDataProvider.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ public boolean accept(String loanProcessingStrategyCode) {
4444

4545
@Override
4646
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
47-
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency) {
47+
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
48+
BigDecimal totalUnpaidPayableDueInterest) {
4849
// Find the current Period (If exists one) based on the Business date
4950
final Optional<LoanSchedulePeriodData> optCurrentPeriod = periods.stream().filter(period -> !period.isDownPaymentPeriod() //
5051
&& period.getPeriod() != null //

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanSummaryDataProvider.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public interface LoanSummaryDataProvider {
3333
BigDecimal computeTotalUnpaidPayableDueInterestAmount(Collection<LoanSchedulePeriodData> periods, LocalDate businessDate);
3434

3535
BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(Loan loan, Collection<LoanSchedulePeriodData> periods,
36-
LocalDate businessDate, CurrencyData currency);
36+
LocalDate businessDate, CurrencyData currency, BigDecimal totalUnpaidPayableDueInterest);
3737

3838
LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData);
3939

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
import java.math.BigDecimal;
2222
import java.time.LocalDate;
2323
import java.util.Collection;
24-
import java.util.Comparator;
2524
import java.util.List;
2625
import java.util.Objects;
26+
import java.util.Optional;
2727
import lombok.AllArgsConstructor;
2828
import lombok.extern.slf4j.Slf4j;
2929
import org.apache.commons.lang3.tuple.Pair;
@@ -40,7 +40,7 @@
4040
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
4141
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
4242
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
43-
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
43+
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
4444
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
4545
import org.springframework.stereotype.Component;
4646

@@ -71,26 +71,25 @@ public LoanSummaryData withTransactionAmountsSummary(Loan loan, LoanSummaryData
7171
return super.withTransactionAmountsSummary(loan, defaultSummaryData, repaymentSchedule, loanTransactionBalances);
7272
}
7373

74-
private LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(Loan loan, LocalDate businessDate) {
74+
private Optional<LoanRepaymentScheduleInstallment> getRelatedRepaymentScheduleInstallment(Loan loan, LocalDate businessDate) {
7575
return loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isDownPayment() && !i.isAdditional()
76-
&& !businessDate.isBefore(i.getFromDate()) && businessDate.isBefore(i.getDueDate())).findFirst().orElseGet(() -> {
77-
List<LoanRepaymentScheduleInstallment> list = loan.getRepaymentScheduleInstallments().stream()
78-
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
79-
return !list.isEmpty() ? list.get(list.size() - 1) : null;
80-
});
76+
&& businessDate.isAfter(i.getFromDate()) && !businessDate.isAfter(i.getDueDate())).findFirst();
8177
}
8278

8379
@Override
8480
public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(final Loan loan,
85-
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency) {
86-
if (loan.isMatured(businessDate)) {
81+
final Collection<LoanSchedulePeriodData> periods, final LocalDate businessDate, final CurrencyData currency,
82+
BigDecimal totalUnpaidPayableDueInterest) {
83+
if (loan.isMatured(businessDate) || !loan.isInterestBearing()) {
8784
return BigDecimal.ZERO;
8885
}
8986

90-
LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = getRelatedRepaymentScheduleInstallment(loan, businessDate);
91-
if (loan.isInterestBearing() && loanRepaymentScheduleInstallment != null) {
87+
Optional<LoanRepaymentScheduleInstallment> currentRepaymentPeriod = getRelatedRepaymentScheduleInstallment(loan, businessDate);
88+
89+
if (currentRepaymentPeriod.isPresent()) {
9290
if (loan.isChargedOff()) {
93-
return loanRepaymentScheduleInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
91+
return MathUtil.subtractToZero(currentRepaymentPeriod.get().getInterestOutstanding(loan.getCurrency()).getAmount(),
92+
totalUnpaidPayableDueInterest);
9493
} else {
9594
List<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
9695
.filter(t -> !t.isAccrualActivity()).toList();
@@ -107,20 +106,15 @@ public BigDecimal computeTotalUnpaidPayableNotDueInterestAmountOnActualPeriod(fi
107106
replayedTransactions);
108107
}
109108
if (model != null) {
110-
LoanRepaymentScheduleInstallment nextUnpaidInAdvanceInstallment = loanRepaymentScheduleInstallment.isNotFullyPaidOff()
111-
? loanRepaymentScheduleInstallment
112-
: loan.getRepaymentScheduleInstallments().stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
113-
.filter(i -> i.getInstallmentNumber() != null)
114-
.min(Comparator.comparingInt(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
115-
if (nextUnpaidInAdvanceInstallment == null) {
116-
return BigDecimal.ZERO;
117-
}
118-
PeriodDueDetails dueAmounts = emiCalculator.getDueAmounts(model, nextUnpaidInAdvanceInstallment.getDueDate(),
119-
businessDate);
120-
if (dueAmounts != null) {
121-
BigDecimal interestPaid = nextUnpaidInAdvanceInstallment.getInterestPaid();
122-
BigDecimal dueInterest = dueAmounts.getDueInterest().getAmount();
123-
return MathUtil.subtractToZero(dueInterest, interestPaid);
109+
OutstandingDetails outstandingDetails = emiCalculator.getOutstandingAmountsTillDate(model, businessDate);
110+
if (!loan.isInterestRecalculationEnabled()) {
111+
BigDecimal interestPaid = periods.stream().map(LoanSchedulePeriodData::getInterestPaid).reduce(BigDecimal.ZERO,
112+
BigDecimal::add);
113+
BigDecimal dueInterest = outstandingDetails.getOutstandingInterest().getAmount();
114+
return MathUtil.subtractToZero(dueInterest, interestPaid, totalUnpaidPayableDueInterest);
115+
} else {
116+
return MathUtil.subtractToZero(outstandingDetails.getOutstandingInterest().getAmount(),
117+
totalUnpaidPayableDueInterest);
124118
}
125119
}
126120
}

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# specific language governing permissions and limitations
1717
# under the License.
1818
#
19-
org.gradle.jvmargs=-Xmx6g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=null
19+
org.gradle.jvmargs=-Xmx12g --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED --add-exports=java.naming/com.sun.jndi.ldap=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.security=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.management/javax.management=ALL-UNNAMED --add-opens=java.naming/javax.naming=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=null
2020
buildType=BUILD
2121
org.gradle.caching=true
2222
org.gradle.parallel=true

0 commit comments

Comments
 (0)